From af1a735c98ccfa4dfd50715d8d66d32085950cdc Mon Sep 17 00:00:00 2001 From: Mark Rogers Date: Thu, 25 Feb 2010 20:07:09 +0000 Subject: [PATCH] Merge DM-DM_deployment to HEAD 17716 : Copied alfresco/HEAD to alfresco/BRANCHES/DEV/BRIAN/DM-DM_deployment. 17721 : Moved alfresco/BRANCHES/DEV/BRIAN/DM-DM_deployment to alfresco/BRANCHES/DEV/DM-DM_deployment. 17809 : Bare Bones of TransferService. 17813 : more bones 17824 : bootstrap of the transfer spaces 17826 : Added username, path, and a getter for the password. 17832 : createTransferTarget, getTransferTargets() working. 17836 : transfer target - duplicate name detection - delete transfer target by name - get transfer target by name - TransferException added along with transfer-service.properties 17840 : transfer target - enable / disable - update properties 17851 : Added the notion of a transmitter for the transfer service, and an HttpClient implementation of it. 17852 : Added the web script needed for the receiving end of a transfer, along with a command processor for clients to check availability of the service and credentials. 17856 : Added a Mockito-based test for HttpClientTransmitterImpl 17857 : Corrected javadoc for test class 17858 : Added test for overriding SSL socket factory 17859 : Wired up the transfer service to the transfer transmitter. 17896 : Rework to spring for app:transferDefinitions it's now app:transfer_definitio - Add throws clauses to TransferService - Bare bones of interface for begin and sendManifest 17944 : Work in progress on manifest file 17947 : added parent and child associations to the snapshot. 17956 : Now has the parent path implemented and introduces the TransferManifestNodeFactory. 17965 : Added content, MLText and collections. 17978 : addition of source and target peer associations. 17982 : Fixing parentNode information. 18008 : XML Manifest Reader checkpoint. (Still incomplete but lots working) 18040 : ParentPath is transmitted, fixes for source and target assocs. 18048 : SAIL-30: Initial commit of functionality for begin, sendManifest, and sendContent parts of the transfer process 18049 : Fix for parse of Locale type. 18054 : Added TransferManifestNodeHelper and more tests 18066 : Work in progress check in - Implemented the content chunker - sketched out more interfaces on TransferTransmitter. - Please note that the chunker is not yet connected to the manifest file, that will come next. r18069 : Wired up manifest reader to content chunker. r18089 : Fiest cut of callback interface for review. r18091 : added hashCode implementation which was missing from ContentData r18095 : Start of the server-side commit. Note that this is an interim commit - not tested. r18096 : Initial entry of a ContentData implementation of HttpClient's "Part" r18155 : Work in progress check in. TransferEvent - incomplete HttpClientTransmitter - first cut complete (not tested) Server side - first cut complete (not tested) 18156 : TransferMessage missing from last check in. 18166 : check in command processors 18167 : Work primarily on the transfer commit operation 18170 : corrected spring errors. 18176 : Further testing and fixing of transfer commit 18206 : Work in progress. 18236 : Work in progress - generally adding debug logging and sorting out exception handlers. 18240 : Fix to call "end" correctly after exception is thrown with "commit" 18242 : Aligning the manifest part names. 18243 : PostSnapshot calls the correct method on the receiver service. 18267 : First node has transferred. 18274 : Fixing abort to call end, debug statements, formatting code brackest 18275 : First code to handle updates. Also improved error messages passed back to client 18289 : Checked in work in progress. Content upload not working. 18290 : Update to ensure file type. 18300 : Added more log output and some of the error messages. 18301 : Work in progress 18302 : Added log output 18307 : Added a noddy transfer action 18315 : Sprint 2 complete - transfer and update one node. 18354 : Now the manifest file has deleted nodes. - Adding copyright headers 18384 : Plumbing for unit tests on one box. 18416 : First end to end unit test working. (one node create and update) 18421 : Added path based update test and many send test. 18458 : Added the functionality to transfer deleted and restored nodes. 18481 : Implementation of transferAsync 18491 : SAIL-32, SAIL-35 - Added node crawler for DM-DM Transfer F6 and F9. 18620 : Basic transfer report implementation git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@18858 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/application-context.xml | 1 + config/alfresco/bootstrap/transferSpaces.xml | 75 + config/alfresco/core-services-context.xml | 1 + config/alfresco/import-export-context.xml | 13 +- .../messages/patch-service.properties | 5 +- .../messages/transfer-service.properties | 22 + config/alfresco/model/transferModel.xml | 134 ++ .../alfresco/patch/patch-services-context.xml | 48 +- config/alfresco/repository.properties | 8 + config/alfresco/transfer-service-context.xml | 124 ++ .../BasicCorrespondingNodeResolverImpl.java | 154 ++ .../CachingCorrespondingNodeResolverImpl.java | 94 ++ .../transfer/ChildAssociatedNodeFinder.java | 209 +++ .../repo/transfer/ContentChunkProcessor.java | 44 + .../repo/transfer/ContentChunker.java | 65 + .../repo/transfer/ContentChunkerImpl.java | 123 ++ .../repo/transfer/ContentChunkerImplTest.java | 109 ++ .../repo/transfer/ContentClassFilter.java | 204 +++ .../repo/transfer/ContentDataPart.java | 132 ++ .../transfer/CorrespondingNodeResolver.java | 50 + .../CorrespondingNodeResolverFactory.java | 40 + ...faultCorrespondingNodeResolverFactory.java | 58 + .../DefaultManifestProcessorFactoryImpl.java | 94 ++ .../org/alfresco/repo/transfer/DeltaList.java | 35 + .../transfer/HttpClientTransmitterImpl.java | 569 ++++++++ .../HttpClientTransmitterImplTest.java | 247 ++++ .../transfer/ManifestProcessorFactory.java | 40 + .../repo/transfer/NodeCrawlerTest.java | 200 +++ .../alfresco/repo/transfer/PathHelper.java | 61 + .../transfer/PeerAssociatedNodeFinder.java | 208 +++ .../RepoPrimaryManifestProcessorImpl.java | 612 ++++++++ .../RepoSecondaryManifestProcessorImpl.java | 292 ++++ .../transfer/RepoTransferReceiverImpl.java | 633 +++++++++ .../RepoTransferReceiverImplTest.java | 475 +++++++ .../transfer/StandardNodeCrawlerImpl.java | 178 +++ .../repo/transfer/TestTransferCallback.java | 36 + .../org/alfresco/repo/transfer/Transfer.java | 95 ++ .../repo/transfer/TransferActionExecuter.java | 81 ++ .../repo/transfer/TransferAsyncAction.java | 88 ++ .../repo/transfer/TransferCommons.java | 51 + .../repo/transfer/TransferEventImpl.java | 140 ++ .../repo/transfer/TransferEventProcessor.java | 221 +++ .../repo/transfer/TransferMessage.java | 31 + .../alfresco/repo/transfer/TransferModel.java | 70 + .../transfer/TransferProcessingException.java | 90 ++ .../repo/transfer/TransferServiceImpl.java | 889 ++++++++++++ .../transfer/TransferServiceImplTest.java | 1265 +++++++++++++++++ .../repo/transfer/TransferTargetImpl.java | 174 +++ .../repo/transfer/TransferTransmitter.java | 102 ++ .../UnitTestInProcessTransmitterImpl.java | 150 ++ .../UnitTestTransferManifestNodeFactory.java | 216 +++ .../manifest/ManifestIntegrationTest.java | 272 ++++ .../repo/transfer/manifest/ManifestModel.java | 61 + .../TestTransferManifestProcessor.java | 89 ++ .../manifest/TransferManifestDeletedNode.java | 62 + .../manifest/TransferManifestHeader.java | 49 + .../manifest/TransferManifestNode.java | 54 + .../manifest/TransferManifestNodeFactory.java | 32 + .../TransferManifestNodeFactoryImpl.java | 130 ++ .../manifest/TransferManifestNodeHelper.java | 81 ++ .../manifest/TransferManifestNormalNode.java | 197 +++ .../manifest/TransferManifestProcessor.java | 68 + .../manifest/TransferManifestTest.java | 214 +++ .../manifest/TransferManifestWriter.java | 73 + .../manifest/XMLTransferManifestReader.java | 596 ++++++++ .../manifest/XMLTransferManifestWriter.java | 432 ++++++ .../repo/transfer/report/TransferReport.xsd | 44 + .../transfer/report/TransferReportModel.java | 43 + .../transfer/report/TransferReporter.java | 49 + .../transfer/report/TransferReporterImpl.java | 151 ++ .../report/XMLTransferReportWriter.java | 119 ++ .../service/cmr/repository/ContentData.java | 13 + .../alfresco/service/cmr/repository/Path.java | 2 +- .../service/cmr/transfer/NodeFilter.java | 50 + .../service/cmr/transfer/NodeFinder.java | 50 + .../cmr/transfer/RangedTransferEvent.java | 49 + .../cmr/transfer/TransferCallback.java | 44 + .../cmr/transfer/TransferDefinition.java | 65 + .../service/cmr/transfer/TransferEvent.java | 38 + .../TransferEventCommittingStatus.java | 41 + .../cmr/transfer/TransferEventEndState.java | 36 + .../cmr/transfer/TransferEventEnterState.java | 36 + .../cmr/transfer/TransferEventError.java | 46 + .../transfer/TransferEventSendingContent.java | 51 + .../TransferEventSendingManifest.java | 40 + .../transfer/TransferEventSentContent.java | 32 + .../cmr/transfer/TransferEventSuccess.java | 38 + .../cmr/transfer/TransferException.java | 60 + .../cmr/transfer/TransferReceiver.java | 107 ++ .../service/cmr/transfer/TransferService.java | 156 ++ .../service/cmr/transfer/TransferTarget.java | 135 ++ .../org/alfresco/service/namespace/QName.java | 6 +- .../util/ThreadPoolExecutorFactoryBean.java | 7 +- 93 files changed, 12954 insertions(+), 20 deletions(-) create mode 100644 config/alfresco/bootstrap/transferSpaces.xml create mode 100644 config/alfresco/messages/transfer-service.properties create mode 100644 config/alfresco/model/transferModel.xml create mode 100644 config/alfresco/transfer-service-context.xml create mode 100644 source/java/org/alfresco/repo/transfer/BasicCorrespondingNodeResolverImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/CachingCorrespondingNodeResolverImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/ChildAssociatedNodeFinder.java create mode 100644 source/java/org/alfresco/repo/transfer/ContentChunkProcessor.java create mode 100644 source/java/org/alfresco/repo/transfer/ContentChunker.java create mode 100644 source/java/org/alfresco/repo/transfer/ContentChunkerImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/ContentChunkerImplTest.java create mode 100644 source/java/org/alfresco/repo/transfer/ContentClassFilter.java create mode 100644 source/java/org/alfresco/repo/transfer/ContentDataPart.java create mode 100644 source/java/org/alfresco/repo/transfer/CorrespondingNodeResolver.java create mode 100644 source/java/org/alfresco/repo/transfer/CorrespondingNodeResolverFactory.java create mode 100644 source/java/org/alfresco/repo/transfer/DefaultCorrespondingNodeResolverFactory.java create mode 100644 source/java/org/alfresco/repo/transfer/DefaultManifestProcessorFactoryImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/DeltaList.java create mode 100644 source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/HttpClientTransmitterImplTest.java create mode 100644 source/java/org/alfresco/repo/transfer/ManifestProcessorFactory.java create mode 100644 source/java/org/alfresco/repo/transfer/NodeCrawlerTest.java create mode 100644 source/java/org/alfresco/repo/transfer/PathHelper.java create mode 100644 source/java/org/alfresco/repo/transfer/PeerAssociatedNodeFinder.java create mode 100644 source/java/org/alfresco/repo/transfer/RepoPrimaryManifestProcessorImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/RepoSecondaryManifestProcessorImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/RepoTransferReceiverImplTest.java create mode 100644 source/java/org/alfresco/repo/transfer/StandardNodeCrawlerImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/TestTransferCallback.java create mode 100644 source/java/org/alfresco/repo/transfer/Transfer.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferActionExecuter.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferAsyncAction.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferCommons.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferEventImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferEventProcessor.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferMessage.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferModel.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferProcessingException.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferServiceImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferTargetImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/TransferTransmitter.java create mode 100644 source/java/org/alfresco/repo/transfer/UnitTestInProcessTransmitterImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/UnitTestTransferManifestNodeFactory.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/ManifestIntegrationTest.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/ManifestModel.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/TestTransferManifestProcessor.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/TransferManifestDeletedNode.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/TransferManifestHeader.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/TransferManifestNode.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeFactory.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeFactoryImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeHelper.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/TransferManifestNormalNode.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/TransferManifestProcessor.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/TransferManifestTest.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/TransferManifestWriter.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestReader.java create mode 100644 source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestWriter.java create mode 100644 source/java/org/alfresco/repo/transfer/report/TransferReport.xsd create mode 100644 source/java/org/alfresco/repo/transfer/report/TransferReportModel.java create mode 100644 source/java/org/alfresco/repo/transfer/report/TransferReporter.java create mode 100644 source/java/org/alfresco/repo/transfer/report/TransferReporterImpl.java create mode 100644 source/java/org/alfresco/repo/transfer/report/XMLTransferReportWriter.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/NodeFilter.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/NodeFinder.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/RangedTransferEvent.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferCallback.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferDefinition.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferEvent.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferEventCommittingStatus.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferEventEndState.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferEventEnterState.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferEventError.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferEventSendingContent.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferEventSendingManifest.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferEventSentContent.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferEventSuccess.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferException.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferReceiver.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferService.java create mode 100644 source/java/org/alfresco/service/cmr/transfer/TransferTarget.java diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index c0e50a2d70..4e5721f8ff 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -16,6 +16,7 @@ --> + alfresco/model/applicationModel.xml diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 9f59b7e648..e2ed67e4b5 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -348,6 +348,11 @@ ${spaces.templates.email.invite1.childname} ${spaces.templates.email.notify.childname} ${spaces.imapConfig.childname} + ${spaces.transfers.childname} + ${spaces.transfer_groups.childname} + ${spaces.transfer_temp.childname} + ${spaces.inbound_transfer_records.childname} + ${spaces.outbound_transfer_records.childname} ${spaces.imap_templates.childname} ${spaces.emailActions.childname} ${spaces.searchAction.childname} @@ -553,7 +558,13 @@ /${spaces.company_home.childname}/${spaces.dictionary.childname} alfresco/bootstrap/imapSpaces.xml alfresco/messages/bootstrap-spaces - + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname} + alfresco/bootstrap/transferSpaces.xml + alfresco/messages/bootstrap-spaces + diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 2d514a57b0..d74533ba79 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -288,4 +288,7 @@ patch.personUsagePatch.result1=Added 'cm:sizeCurrent' property to {0} people tha patch.personUsagePatch.result2=No people were missing the 'cm:sizeCurrent' property. patch.redeployNominatedInvitationProcessWithPropsForShare.description=Redeploy nominated invitation workflow -patch.redeployNominatedInvitationProcessWithPropsForShare.result=Nominated invitation workflow redeployed \ No newline at end of file +patch.redeployNominatedInvitationProcessWithPropsForShare.result=Nominated invitation workflow redeployed + +patch.transferDefinitions.description=Add transfer definitions folder to data dictionary. +patch.transferDefinitions.result=Transfer definitions folder added to data dictionary. diff --git a/config/alfresco/messages/transfer-service.properties b/config/alfresco/messages/transfer-service.properties new file mode 100644 index 0000000000..7443992707 --- /dev/null +++ b/config/alfresco/messages/transfer-service.properties @@ -0,0 +1,22 @@ +# Transfer service externalised display strings + +transfer_service.unable_to_find_transfer_home=Unable to find transfer home: {0} +transfer_service.unable_to_find_transfer_group=Unable to find transfer group with name, {0} +transfer_service.unable_to_find_transfer_target=Unable to find transfer target with name, {0} +transfer_service.target_exists=Unable to create a new transfer target with the name, {0}, since there is already a target with that name. +transfer_service.comms.unsupported_protocol=Unsupported protocol: {0} +transfer_service.comms.unsuccessful_response=Received unsuccessful response code from target server: {0}, {1} +transfer_service.comms.http_request_failed=Failed to execute HTTP request {0} to target: {1} status: {2} +transfer_service.no_nodes=No nodes to transfer + +transfer_service.receiver.failed_to_create_staging_folder=Unable to create staging directory for transfer {0} +transfer_service.receiver.lock_folder_not_found=Unable to locate specified lock folder: {0} +transfer_service.receiver.temp_folder_not_found=Unable to locate specified temporary folder for transfer {0}: {1} +transfer_service.receiver.lock_unavailable=Transfer lock has been claimed for another inbound transfer. Unable to start new transfer. +transfer_service.receiver.record_folder_not_found=Failed to find folder specified to hold inbound transfer records: {0} +transfer_service.receiver.not_lock_owner=Failed attempt to carry out transfer operation. Lock not held by specified transfer: {0} +transfer_service.receiver.error_ending_transfer= +transfer_service.receiver.error_staging_snapshot= +transfer_service.receiver.error_staging_content= +transfer_service.receiver.no_snapshot_received= +transfer_service.receiver.error_committing_transfer= diff --git a/config/alfresco/model/transferModel.xml b/config/alfresco/model/transferModel.xml new file mode 100644 index 0000000000..a428d2c9d0 --- /dev/null +++ b/config/alfresco/model/transferModel.xml @@ -0,0 +1,134 @@ + + + Alfresco Transfer Application Model + Alfresco + 2009-12-16 + 1.0 + + + + + + + + + + + + + + + + http + https + + + + + + + + + Transfer Group + The definition of a transfer group + cm:folder + + + + + + + Transfer Target + The definition of a transfer target + cm:folder + + + + Endpoint Host + d:text + true + + true + false + false + + + + + Endpoint Port + d:int + true + + + + Endpoint Path + d:text + true + + + + Endpoint Protocol + d:text + true + + + + + + + Username + d:text + + + + + Password + d:any + + true + false + false + + + + + + + + Transfer Lock + Node type used to represent the transfer lock node + cm:content + + + + Locked Transfer Identifier + d:text + true + + + + + + + Transfer Report + Transfer Report + cm:content + + + + + + + + Can this resource be enabled/disabled. + + + Is this enabled. + d:boolean + true + + + + + + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index ac11f9d906..44cbd114cc 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1991,7 +1991,7 @@ - + patch.db-V3.2-ContentTables2 patch.schemaUpgradeScript.description @@ -2008,16 +2008,38 @@ - - - patch.db-V3.3-Remove-VersionCount - patch.schemaUpgradeScript.description - 0 - 4002 - 4003 - - classpath:alfresco/dbscripts/upgrade/3.3/${db.script.dialect}/remove-VersionCount.sql - - - + + + patch.db-V3.3-Remove-VersionCount + patch.schemaUpgradeScript.description + 0 + 4002 + 4003 + + classpath:alfresco/dbscripts/upgrade/3.3/${db.script.dialect}/remove-VersionCount.sql + + + + + patch.transferServiceFolder + patch.transferDefinitions.description + 0 + 3007 + 3008 + + + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.transfers.childname} + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname} + alfresco/bootstrap/transferSpaces.xml + alfresco/messages/bootstrap-spaces + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index fd46783da8..92636e5163 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -313,6 +313,11 @@ spaces.user_homes.childname=app:user_homes spaces.sites.childname=st:sites spaces.templates.email.invite.childname=cm:invite spaces.wcm_deployed.childname=cm:wcm_deployed +spaces.transfers.childname=app:transfers +spaces.transfer_groups.childname=app:transfer_groups +spaces.transfer_temp.childname=app:temp +spaces.inbound_transfer_records.childname=app:inbound_transfer_records +spaces.outbound_transfer_records.childname=app:outbound_transfer_records # ADM VersionStore Configuration version.store.deprecated.lightWeightVersionStore=workspace://lightWeightVersionStore @@ -447,3 +452,6 @@ deployment.service.numberOfSendingThreads=5 deployment.service.corePoolSize=2 deployment.service.maximumPoolSize=3 +# Transfer Service +transferservice.receiver.enabled=true +transferservice.receiver.stagingDir=${java.io.tmpdir}/alfresco-transfer-staging diff --git a/config/alfresco/transfer-service-context.xml b/config/alfresco/transfer-service-context.xml new file mode 100644 index 0000000000..9e3fc7a0d6 --- /dev/null +++ b/config/alfresco/transfer-service-context.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.transfers.childname}/${spaces.transfer_groups.childname} + + + Default Group + + + + + + + + + + + + + + + + + + + + + + + + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.transfers.childname} + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.transfers.childname}/${spaces.transfer_temp.childname} + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.transfers.childname}/${spaces.inbound_transfer_records.childname} + ${transferservice.receiver.stagingDir} + + + + + + + + + + + + + + + + + + + alfresco.messages.transfer-service + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + org.alfresco.service.cmr.transfer.TransferService + + + + + + + + + + + + + + + + + + + + deployment + + + + + + false + + + + + + diff --git a/source/java/org/alfresco/repo/transfer/BasicCorrespondingNodeResolverImpl.java b/source/java/org/alfresco/repo/transfer/BasicCorrespondingNodeResolverImpl.java new file mode 100644 index 0000000000..3fdb275643 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/BasicCorrespondingNodeResolverImpl.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.util.List; + +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.Path.Element; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author brian + * + */ +public class BasicCorrespondingNodeResolverImpl implements CorrespondingNodeResolver +{ + private static final Log log = LogFactory.getLog(BasicCorrespondingNodeResolverImpl.class); + + private static final String MSG_SPECIFIED_STORE_DOES_NOT_EXIST = "transfer_service.receiver.specified_store_nonexistent"; + private NodeService nodeService; + + public ResolvedParentChildPair resolveCorrespondingNode(NodeRef sourceNodeRef, ChildAssociationRef primaryAssoc, + Path parentPath) + { + if (log.isDebugEnabled()) { + log.debug("Attempting to resolve corresponding node for noderef " + sourceNodeRef); + log.debug("Supplied parent path: " + parentPath); + log.debug("Supplied parent assoc: " + primaryAssoc.toString()); + } + ResolvedParentChildPair result = new ResolvedParentChildPair(null, null); + + // Does a node with the same NodeRef already exist in this repo? + if (nodeService.exists(sourceNodeRef)) + { + result.resolvedChild = sourceNodeRef; + } + + // Find where this node should live + NodeRef parentNodeRef = primaryAssoc.getParentRef(); + if (!nodeService.exists(parentNodeRef.getStoreRef())) + { + throw new TransferProcessingException(MSG_SPECIFIED_STORE_DOES_NOT_EXIST); + } + if (!nodeService.exists(parentNodeRef)) + { + if (log.isDebugEnabled()) + { + log.debug("Unable to find node's parent by node ref: " + parentNodeRef); + } + parentNodeRef = resolveParentPath(primaryAssoc.getParentRef().getStoreRef(), parentPath); + } + else + { + if (log.isDebugEnabled()) + { + log.debug("Node's parent has been resolved by noderef: " + parentNodeRef); + } + } + if (log.isDebugEnabled()) + { + log.debug("Parent noderef resolved to node: " + parentNodeRef); + } + result.resolvedParent = parentNodeRef; + if ((parentNodeRef != null) && (result.resolvedChild == null)) + { + //We've managed to find the approprate parent node, but not the child. + //See if we can find the child by looking at the parent's child associations + List children = nodeService.getChildAssocs(parentNodeRef, RegexQNamePattern.MATCH_ALL, + primaryAssoc.getQName()); + if (!children.isEmpty()) + { + result.resolvedChild = children.get(0).getChildRef(); + } + } + if (log.isDebugEnabled()) + { + log.debug("Resolved child node to: " + result.resolvedChild); + } + return result; + } + + /** + * @param parentPath + */ + private NodeRef resolveParentPath(StoreRef store, Path parentPath) + { + if (log.isDebugEnabled()) + { + log.debug("Trying to resolve parent path " + parentPath); + } + NodeRef node = nodeService.getRootNode(store); + int index = 1; + while (index < parentPath.size()) + { + Element element = parentPath.get(index++); + QName name = QName.createQName(element.getElementString()); + List children = nodeService.getChildAssocs(node, RegexQNamePattern.MATCH_ALL, name); + + if (children.isEmpty()) + { + if (log.isDebugEnabled()) + { + log.debug("Failed to resolve path element " + element.getElementString()); + } + return null; + } + if (log.isDebugEnabled()) + { + log.debug("Resolved path element " + element.getElementString()); + } + node = children.get(0).getChildRef(); + } + return node; + } + + /** + * @param nodeService the nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/CachingCorrespondingNodeResolverImpl.java b/source/java/org/alfresco/repo/transfer/CachingCorrespondingNodeResolverImpl.java new file mode 100644 index 0000000000..8e11c768f4 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/CachingCorrespondingNodeResolverImpl.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author brian + * + */ +public class CachingCorrespondingNodeResolverImpl implements CorrespondingNodeResolver +{ + private static final Log log = LogFactory.getLog(CachingCorrespondingNodeResolverImpl.class); + + private Map cache = new HashMap(359); + private CorrespondingNodeResolver delegateResolver; + + public CachingCorrespondingNodeResolverImpl() + { + + } + + /** + * @param delegateResolver + */ + public CachingCorrespondingNodeResolverImpl(CorrespondingNodeResolver delegateResolver) + { + super(); + this.delegateResolver = delegateResolver; + } + + public ResolvedParentChildPair resolveCorrespondingNode(NodeRef sourceNodeRef, ChildAssociationRef primaryAssoc, + Path parentPath) + { + + ResolvedParentChildPair result = cache.get(sourceNodeRef); + + if (result != null) + { + if (log.isDebugEnabled()) + { + log.debug("Found fully-resolved entry in cache for node " + sourceNodeRef); + } + return result; + } + + result = delegateResolver.resolveCorrespondingNode(sourceNodeRef, primaryAssoc, parentPath); + + //If we have fully resolved the parent and child nodes then stick it in the cache... + if (result.resolvedChild != null && result.resolvedParent != null) + { + cache.put(sourceNodeRef, result); + } + return result; + } + + /** + * @param delegateResolver the delegateResolver to set + */ + public void setDelegateResolver(CorrespondingNodeResolver delegateResolver) + { + this.delegateResolver = delegateResolver; + } +} diff --git a/source/java/org/alfresco/repo/transfer/ChildAssociatedNodeFinder.java b/source/java/org/alfresco/repo/transfer/ChildAssociatedNodeFinder.java new file mode 100644 index 0000000000..44e83f0f80 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/ChildAssociatedNodeFinder.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.transfer.NodeFinder; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.ParameterCheck; + +/** + * @author brian + * + */ +public class ChildAssociatedNodeFinder implements NodeFinder +{ + private Set suppliedAssociationTypes = new HashSet(); + private boolean exclude = false; + private boolean initialised = false; + private List childAssociationTypes = new ArrayList(); + private ServiceRegistry serviceRegistry; + + public ChildAssociatedNodeFinder() + { + } + + public ChildAssociatedNodeFinder(Set associationTypeNames) + { + setAssociationTypes(associationTypeNames); + } + + public ChildAssociatedNodeFinder(QName... associationTypeNames) + { + setAssociationTypes(associationTypeNames); + } + + public ChildAssociatedNodeFinder(Set associationTypeNames, boolean exclude) + { + setAssociationTypes(associationTypeNames); + this.exclude = exclude; + } + + public ChildAssociatedNodeFinder(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + public ChildAssociatedNodeFinder(ServiceRegistry serviceRegistry, Set associationTypeNames) + { + this(serviceRegistry); + setAssociationTypes(associationTypeNames); + } + + public ChildAssociatedNodeFinder(ServiceRegistry serviceRegistry, QName... associationTypeNames) + { + this(serviceRegistry); + setAssociationTypes(associationTypeNames); + } + + public ChildAssociatedNodeFinder(ServiceRegistry serviceRegistry, Set associationTypeNames, boolean exclude) + { + setAssociationTypes(associationTypeNames); + this.exclude = exclude; + } + + public void setAssociationTypes(QName... associationTypes) + { + setAssociationTypes(Arrays.asList(associationTypes)); + } + + public void setAssociationTypes(Collection associationTypes) + { + this.suppliedAssociationTypes = new HashSet(associationTypes); + initialised = false; + } + + /** + * @param exclude + * the exclude to set + */ + public void setExclude(boolean exclude) + { + this.exclude = exclude; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.service.cmr.transfer.NodeFinder#findFrom(org.alfresco.service.cmr.repository.NodeRef, + * org.alfresco.service.ServiceRegistry) + */ + public Set findFrom(NodeRef thisNode) + { + if (!initialised) + { + init(); + } + if (exclude) + { + return processExcludedSet(thisNode); + } + else + { + return processIncludedSet(thisNode); + } + } + + /** + * @param thisNode + * @param serviceRegistry + * @return + */ + private Set processExcludedSet(NodeRef thisNode) + { + Set results = new HashSet(89); + NodeService nodeService = serviceRegistry.getNodeService(); + + // Find all the child nodes (filtering as necessary). + List children = nodeService.getChildAssocs(thisNode); + boolean filterChildren = !childAssociationTypes.isEmpty(); + for (ChildAssociationRef child : children) + { + if (!filterChildren || !childAssociationTypes.contains(child.getTypeQName())) + { + results.add(child.getChildRef()); + } + } + return results; + } + + private Set processIncludedSet(NodeRef startingNode) + { + NodeService nodeService = serviceRegistry.getNodeService(); + Set foundNodes = new HashSet(89); + for (QName assocType : childAssociationTypes) + { + List children = nodeService.getChildAssocs(startingNode, assocType, + RegexQNamePattern.MATCH_ALL); + for (ChildAssociationRef child : children) + { + foundNodes.add(child.getChildRef()); + } + } + return foundNodes; + } + + /** + * @param serviceRegistry + * the serviceRegistry to set + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + public void init() + { + ParameterCheck.mandatory("serviceRegistry", serviceRegistry); + // Quickly scan the supplied association types and remove any that either + // do not exist or are not child association types. + DictionaryService dictionaryService = serviceRegistry.getDictionaryService(); + childAssociationTypes.clear(); + for (QName associationType : suppliedAssociationTypes) + { + AssociationDefinition assocDef = dictionaryService.getAssociation(associationType); + if (assocDef != null && assocDef.isChild()) + { + childAssociationTypes.add(associationType); + } + } + initialised = true; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/ContentChunkProcessor.java b/source/java/org/alfresco/repo/transfer/ContentChunkProcessor.java new file mode 100644 index 0000000000..3826f1c10d --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/ContentChunkProcessor.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import java.util.Set; + +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.transfer.TransferException; + +/** + * + * @author Mark Rogers + */ +public interface ContentChunkProcessor +{ + /** + * process this chunk of content data + * @param data + */ + public void processChunk(Set data) throws TransferException; + +} diff --git a/source/java/org/alfresco/repo/transfer/ContentChunker.java b/source/java/org/alfresco/repo/transfer/ContentChunker.java new file mode 100644 index 0000000000..83e777aa1b --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/ContentChunker.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.transfer.TransferException; + +/** + * The Content Chunker Splits Content into "Chunks" of a given size + * + * @author Mark + */ +public interface ContentChunker +{ + /** + * add content data to the chunker + */ + public void addContent(ContentData data) throws TransferException; + + /** + * flush any remaining content data + */ + public void flush() throws TransferException; + + /** + * + * @param chunkSize + */ + public void setChunkSize(long chunkSize); + + /** + * + * @return + */ + public long getChunkSize(); + + /** + * + * @param handler + */ + public void setHandler(ContentChunkProcessor handler); + +} diff --git a/source/java/org/alfresco/repo/transfer/ContentChunkerImpl.java b/source/java/org/alfresco/repo/transfer/ContentChunkerImpl.java new file mode 100644 index 0000000000..721799ff2b --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/ContentChunkerImpl.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.alfresco.repo.site.SiteServiceImpl; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.transfer.TransferException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The Content Chunker Splits Content into "Chunks" of a given size. + * + * @author Mark + */ +public class ContentChunkerImpl implements ContentChunker +{ + private static Log logger = LogFactory.getLog(ContentChunkerImpl.class); + + /** + * The chunk size. + */ + private long chunkSize = 1000000; + + /** + * The handler to recieve the "chunks" + */ + private ContentChunkProcessor handler; + + /** + * The internal buffer + */ + private Set buffer = new HashSet(); + + /** + * + */ + public void addContent(ContentData data) throws TransferException + { + logger.debug("add content size:" + data.getSize()); + buffer.add(data); + + /** + * work out whether the buffer has filled up and needs to be flushed + */ + Iterator iter = buffer.iterator(); + long totalContentSize = 0; + + while (iter.hasNext()) + { + ContentData x = (ContentData)iter.next(); + totalContentSize += x.getSize(); + } + if(logger.isDebugEnabled()) + { + logger.debug("elements " + buffer.size() + ", totalContentSize:" + totalContentSize); + } + if(totalContentSize >= chunkSize) + { + flush(); + } + } + + /** + * + */ + public void flush() throws TransferException + { + logger.debug("flush number of contents:" + buffer.size()); + if(buffer.size() > 0) + { + handler.processChunk(buffer); + } + buffer.clear(); + logger.debug("buffer empty"); + } + + public void setChunkSize(long chunkSize) + { + this.chunkSize = chunkSize; + } + + public long getChunkSize() + { + return chunkSize; + } + + public void setHandler(ContentChunkProcessor handler) + { + this.handler = handler; + } + + public ContentChunkProcessor getHandler() + { + return handler; + } +} diff --git a/source/java/org/alfresco/repo/transfer/ContentChunkerImplTest.java b/source/java/org/alfresco/repo/transfer/ContentChunkerImplTest.java new file mode 100644 index 0000000000..cae0ae78fc --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/ContentChunkerImplTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.service.cmr.repository.ContentData; + +import junit.framework.TestCase; + +/** + * Unit test of the Content Chunker + * + * @author Mark Rogers + */ +public class ContentChunkerImplTest extends TestCase +{ + public void testContentChunkerImpl() throws Exception + { + ContentChunker chunker = new ContentChunkerImpl(); + + final SetprocessedContent = new HashSet(); + + chunker.setHandler( + new ContentChunkProcessor(){ + public void processChunk(Set data) + { + processedContent.addAll(data); + } + } + ); + + /** + * Set a chunk size of 10 bytes + */ + chunker.setChunkSize(10); + + /** + * add one contet of size 20, should flush immediatly + */ + chunker.addContent(new ContentData(null, null, 20, null)); + assertTrue("size 20 not written immediatley", processedContent.size() == 1); + + /** + * add one content of size 1 - should remain buffered in chunker + */ + processedContent.clear(); + chunker.addContent(new ContentData(null, null, 1, null)); + assertTrue("size 1 not buffered", processedContent.size() == 0); + + /** + * flush should write it out + */ + chunker.flush(); + assertTrue("size 1 not flushed", processedContent.size() == 1); + + /** + * Now test the boundary condition over the threashold + */ + processedContent.clear(); + for(int i = 0; i < 11 ; i++) + { + chunker.addContent(new ContentData(null, null, 1, null)); + } + assertEquals("size 10 not buffered", processedContent.size(), 10); + + /** + * flush should write it out + */ + chunker.flush(); + assertTrue("size 1 not flushed", processedContent.size() == 11); + + + /** + * Now Just whack some load through + */ + processedContent.clear(); + for(int i = 0; i < 100 ; i++) + { + chunker.addContent(new ContentData(null, null, 3, null)); + } + chunker.flush(); + assertEquals("size 100 not written", processedContent.size(), 100); + + } +} diff --git a/source/java/org/alfresco/repo/transfer/ContentClassFilter.java b/source/java/org/alfresco/repo/transfer/ContentClassFilter.java new file mode 100644 index 0000000000..7493f72b87 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/ContentClassFilter.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.transfer.NodeFilter; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ParameterCheck; + +/** + * @author brian + * + */ +public class ContentClassFilter implements NodeFilter +{ + private Set contentClasses = new HashSet(); + private Set aspects = new HashSet(); + private Set types = new HashSet(); + private boolean initialised = false; + private boolean exclude = false; + private boolean directOnly = false; + private ServiceRegistry serviceRegistry; + + public ContentClassFilter() + { + } + + public ContentClassFilter(QName... contentClasses) + { + setContentClasses(contentClasses); + } + + public ContentClassFilter(ServiceRegistry serviceRegistry) + { + this(); + this.serviceRegistry = serviceRegistry; + } + + public ContentClassFilter(ServiceRegistry serviceRegistry, QName... contentClasses) + { + this(serviceRegistry); + setContentClasses(contentClasses); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.service.cmr.transfer.NodeFilter#accept(org.alfresco.service.cmr.repository.NodeRef, + * org.alfresco.service.ServiceRegistry) + */ + public boolean accept(NodeRef thisNode) + { + if (!initialised) + { + init(); + } + NodeService nodeService = serviceRegistry.getNodeService(); + + Set nodesAspects = nodeService.getAspects(thisNode); + QName type = nodeService.getType(thisNode); + + boolean typeIsInSet = types.contains(type); + boolean aspectIsInSet = false; + for (QName aspect : nodesAspects) + { + if (aspects.contains(aspect)) + { + aspectIsInSet = true; + break; + } + } + return (!exclude && (typeIsInSet || aspectIsInSet)) || (exclude && (!typeIsInSet && !aspectIsInSet)); + } + + /** + * @param serviceRegistry + * the serviceRegistry to set + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * @param serviceRegistry + */ + public void init() + { + ParameterCheck.mandatory("serviceRegistry", serviceRegistry); + DictionaryService dictionaryService = serviceRegistry.getDictionaryService(); + aspects.clear(); + types.clear(); + for (QName contentClass : contentClasses) + { + ClassDefinition classDef = dictionaryService.getClass(contentClass); + if (classDef == null) + { + continue; + } + if (classDef.isAspect()) + { + aspects.add(contentClass); + if (!directOnly) + { + aspects.addAll(dictionaryService.getSubAspects(contentClass, true)); + } + } + else + { + types.add(contentClass); + if (!directOnly) + { + types.addAll(dictionaryService.getSubTypes(contentClass, true)); + } + } + + } + initialised = true; + } + + /** + * Set the classes of content (types and aspects) to filter by. + * + * @param contentClasses + * the contentClasses to set + */ + public void setContentClasses(Collection contentClasses) + { + this.contentClasses = new HashSet(contentClasses); + initialised = false; + } + + /** + * Set the classes of content (types and aspects) to filter by. + * + * @param contentClasses + * the contentClasses to set + */ + public void setContentClasses(QName... contentClasses) + { + this.contentClasses = new HashSet(Arrays.asList(contentClasses)); + initialised = false; + } + + /** + * Specify whether the filter should exclude the specified classes of content. + * + * @param exclude + * If true then this filter will not accept content that is of any of the filtered classes of content. If + * false then this filter will only accept content that has one or more of the filtered classes of + * content. Defaults to false. + */ + public void setExclude(boolean exclude) + { + this.exclude = exclude; + } + + /** + * Specify whether the filter should only test against the content classes that have been supplied, or if it should + * also test against all subclasses of those classes. For example, if "directOnly" is true and "cm:content" is in + * the set of content classes then a node of type "cm:thumnail" will not be filtered. + * + * @param directOnly + * If true then the filter only filters specifically the specified content classes. Defaults to false. + */ + public void setDirectOnly(boolean directOnly) + { + this.directOnly = directOnly; + initialised = false; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/ContentDataPart.java b/source/java/org/alfresco/repo/transfer/ContentDataPart.java new file mode 100644 index 0000000000..36ad04b7fd --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/ContentDataPart.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.apache.commons.httpclient.methods.multipart.PartBase; +import org.apache.commons.httpclient.util.EncodingUtil; + +/** + * @author brian + * + */ +public class ContentDataPart extends PartBase +{ + /** Attachment's file name */ + protected static final String FILE_NAME = "; filename="; + + /** Attachment's file name as a byte array */ + private static final byte[] FILE_NAME_BYTES = + EncodingUtil.getAsciiBytes(FILE_NAME); + + private ContentService contentService; + private ContentData data; + private String filename; + + /** + * ContentDataPart + * @param contentService + * @param partName + * @param data + */ + public ContentDataPart(ContentService contentService, String partName, ContentData data) { + super(partName, data.getMimetype(), data.getEncoding(), null); + this.contentService = contentService; + this.data = data; + this.filename = partName; + } + + /** + * Write the disposition header to the output stream + * @param out The output stream + * @throws IOException If an IO problem occurs + * @see Part#sendDispositionHeader(OutputStream) + */ + protected void sendDispositionHeader(OutputStream out) + throws IOException { + super.sendDispositionHeader(out); + if (filename != null) { + out.write(FILE_NAME_BYTES); + out.write(QUOTE_BYTES); + out.write(EncodingUtil.getAsciiBytes(filename)); + out.write(QUOTE_BYTES); + } + } + + /* (non-Javadoc) + * @see org.apache.commons.httpclient.methods.multipart.Part#lengthOfData() + */ + @Override + protected long lengthOfData() throws IOException + { + return data.getSize(); + } + + /* (non-Javadoc) + * @see org.apache.commons.httpclient.methods.multipart.Part#sendData(java.io.OutputStream) + */ + @Override + protected void sendData(OutputStream out) throws IOException + { + + // Get the content from the content URL and write it to out + + // Can't use the following code since it closes 'out', which needs to remain open. + // AbstractContentReader. + // contentService.getRawReader(data.getContentUrl()).getContent(out); + + InputStream is = contentService.getRawReader(data.getContentUrl()).getContentInputStream(); + +// ReadableByteChannel rbc = Channels.newChannel(is); +// WritableByteChannel wbc = Channels.newChannel(out); + + try + { + byte[] tmp = new byte[4096]; + int len; + while ((len = is.read(tmp)) >= 0) + { + out.write(tmp, 0, len); + } + } + finally + { + // we're done with the input stream, close it + is.close(); + } + + + + } +} diff --git a/source/java/org/alfresco/repo/transfer/CorrespondingNodeResolver.java b/source/java/org/alfresco/repo/transfer/CorrespondingNodeResolver.java new file mode 100644 index 0000000000..763aafd7ad --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/CorrespondingNodeResolver.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; + +/** + * @author brian + * + */ +public interface CorrespondingNodeResolver +{ + ResolvedParentChildPair resolveCorrespondingNode(NodeRef sourceNodeRef, ChildAssociationRef primaryAssoc, + Path parentPath); + + static class ResolvedParentChildPair { + public NodeRef resolvedParent; + public NodeRef resolvedChild; + + public ResolvedParentChildPair(NodeRef parent, NodeRef child) { + this.resolvedParent = parent; + this.resolvedChild = child; + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/transfer/CorrespondingNodeResolverFactory.java b/source/java/org/alfresco/repo/transfer/CorrespondingNodeResolverFactory.java new file mode 100644 index 0000000000..c289d7b7a6 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/CorrespondingNodeResolverFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +/** + * @author brian + * + */ +public interface CorrespondingNodeResolverFactory +{ + + /** + * @return + */ + CorrespondingNodeResolver getResolver(); + +} diff --git a/source/java/org/alfresco/repo/transfer/DefaultCorrespondingNodeResolverFactory.java b/source/java/org/alfresco/repo/transfer/DefaultCorrespondingNodeResolverFactory.java new file mode 100644 index 0000000000..a69c28beff --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/DefaultCorrespondingNodeResolverFactory.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import org.alfresco.service.cmr.repository.NodeService; + +/** + * @author brian + * + */ +public class DefaultCorrespondingNodeResolverFactory implements CorrespondingNodeResolverFactory +{ + private NodeService nodeService; + + /* (non-Javadoc) + * @see org.alfresco.repo.transfer.CorrespondingNodeResolverFactory#getResolver() + */ + public CorrespondingNodeResolver getResolver() + { + BasicCorrespondingNodeResolverImpl basicResolver = new BasicCorrespondingNodeResolverImpl(); + basicResolver.setNodeService(nodeService); + CachingCorrespondingNodeResolverImpl cachingResolver = new CachingCorrespondingNodeResolverImpl(basicResolver); + return cachingResolver; + } + + /** + * @param nodeService the nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + +} diff --git a/source/java/org/alfresco/repo/transfer/DefaultManifestProcessorFactoryImpl.java b/source/java/org/alfresco/repo/transfer/DefaultManifestProcessorFactoryImpl.java new file mode 100644 index 0000000000..dd0c65ae7a --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/DefaultManifestProcessorFactoryImpl.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.repo.transfer.manifest.TransferManifestProcessor; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.transfer.TransferReceiver; + +/** + * @author brian + * + */ +public class DefaultManifestProcessorFactoryImpl implements ManifestProcessorFactory +{ + private NodeService nodeService; + private ContentService contentService; + private CorrespondingNodeResolverFactory nodeResolverFactory; + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.transfer.ManifestProcessorFactory#getPrimaryCommitProcessor() + */ + public List getCommitProcessors(TransferReceiver receiver, String transferId) + { + List processors = new ArrayList(); + CorrespondingNodeResolver nodeResolver = nodeResolverFactory.getResolver(); + + RepoPrimaryManifestProcessorImpl primaryProcessor = new RepoPrimaryManifestProcessorImpl(receiver, transferId); + primaryProcessor.setContentService(contentService); + primaryProcessor.setNodeResolver(nodeResolver); + primaryProcessor.setNodeService(nodeService); + processors.add(primaryProcessor); + + RepoSecondaryManifestProcessorImpl secondaryProcessor = new RepoSecondaryManifestProcessorImpl(transferId); + secondaryProcessor.setNodeResolver(nodeResolver); + secondaryProcessor.setNodeService(nodeService); + processors.add(secondaryProcessor); + + return processors; + } + + /** + * @param nodeService the nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param contentService the contentService to set + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @param nodeResolverFactory the nodeResolverFactory to set + */ + public void setNodeResolverFactory(CorrespondingNodeResolverFactory nodeResolverFactory) + { + this.nodeResolverFactory = nodeResolverFactory; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/DeltaList.java b/source/java/org/alfresco/repo/transfer/DeltaList.java new file mode 100644 index 0000000000..4273647a51 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/DeltaList.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +/** + * Details back from the manifest to say which nodes the remote server already has. + * + * @author Mark Rogers + */ +public class DeltaList +{ + +} diff --git a/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java new file mode 100644 index 0000000000..031239eaa7 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImpl.java @@ -0,0 +1,569 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.io.File; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.cmr.transfer.TransferTarget; +import org.apache.commons.httpclient.HostConfiguration; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpState; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.multipart.FilePart; +import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; +import org.apache.commons.httpclient.methods.multipart.Part; +import org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * HTTP implementation of TransferTransmitter. + * + * Sends data via HTTP to the server. + * + * @author brian + */ +public class HttpClientTransmitterImpl implements TransferTransmitter +{ + private static final Log log = LogFactory.getLog(HttpClientTransmitterImpl.class); + + private static final String MSG_UNSUPPORTED_PROTOCOL = "transfer_service.comms.unsupported_protocol"; + private static final String MSG_UNSUCCESSFUL_RESPONSE = "transfer_service.comms.unsuccessful_response"; + private static final String MSG_HTTP_REQUEST_FAILED = "transfer_service.comms.http_request_failed"; + + private static final int DEFAULT_HTTP_PORT = 80; + private static final int DEFAULT_HTTPS_PORT = 443; + private static final String HTTP_SCHEME_NAME = "http"; // lowercase is important + private static final String HTTPS_SCHEME_NAME = "https"; // lowercase is important + + private HttpClient httpClient = null; + private Protocol httpProtocol = new Protocol(HTTP_SCHEME_NAME, new DefaultProtocolSocketFactory(), DEFAULT_HTTP_PORT); + private Protocol httpsProtocol = new Protocol(HTTPS_SCHEME_NAME, (ProtocolSocketFactory) new SSLProtocolSocketFactory(), DEFAULT_HTTPS_PORT); + private Map protocolMap = null; + + private ContentService contentService; + + public HttpClientTransmitterImpl() + { + protocolMap = new TreeMap(); + protocolMap.put(HTTP_SCHEME_NAME, httpProtocol); + protocolMap.put(HTTPS_SCHEME_NAME, httpsProtocol); + + httpClient = new HttpClient(); + httpClient.setHttpConnectionManager(new MultiThreadedHttpConnectionManager()); + } + + public void init() + { + } + + /** + * By default this class uses the standard SSLProtocolSocketFactory, but this method allows this to be overridden. + * Useful if, for example, one wishes to permit support of self-signed certificates on the target. + * @param socketFactory + */ + public void setHttpsSocketFactory(ProtocolSocketFactory socketFactory) + { + protocolMap.put(HTTPS_SCHEME_NAME, new Protocol(HTTPS_SCHEME_NAME, socketFactory, DEFAULT_HTTPS_PORT)); + } + + /** + * By default, this class uses a plain HttpClient instance with the only non-default + * option being the multi-threaded connection manager. + * Use this method to replace this with your own HttpClient instance configured how you wish + * @param httpClient + */ + public void setHttpClient(HttpClient httpClient) + { + this.httpClient = httpClient; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.transfer.Transmitter#verifyTarget(org.alfresco.service.cmr.transfer.TransferTarget) + */ + public void verifyTarget(TransferTarget target) throws TransferException + { + HttpMethod verifyRequest = new PostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + verifyRequest.setPath(target.getEndpointPath() + "/test"); + try + { + int response = httpClient.executeMethod(hostConfig, verifyRequest, httpState); + checkResponseStatus("verifyTarget", response, verifyRequest); + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"verifyTraget", target.toString(), e.toString()}, e); + } + } + finally + { + verifyRequest.releaseConnection(); + } + } + + /** + * @param response + */ + private void checkResponseStatus(String methodName, int response, HttpMethod method) + { + if (response != 200) + { + String errorId = null; + String[] errorParams = null; + try { + log.error("Received \"unsuccessful\" response code from target server: " + response); + String errorPayload = method.getResponseBodyAsString(); + JSONObject errorObj = new JSONObject(errorPayload); + errorId = errorObj.getString("errorId"); + JSONArray errorParamArray = errorObj.getJSONArray("errorParams"); + int length = errorParamArray.length(); + errorParams = new String[length]; + for (int i = 0; i < length; ++i) + { + errorParams[i] = errorParamArray.getString(i); + } + } catch (Exception ex) { + throw new TransferException(MSG_UNSUCCESSFUL_RESPONSE, new Object[] {methodName, response}); + } + throw new TransferException(errorId, errorParams); + } + } + + /** + * Get the HTTPState for a transfer target + * @param target + * @return + */ + private HttpState getHttpState(TransferTarget target) + { + HttpState httpState = new HttpState(); + httpState.setCredentials(new AuthScope(target.getEndpointHost(), target.getEndpointPort(), + AuthScope.ANY_REALM), + new UsernamePasswordCredentials(target.getUsername(), new String(target.getUsername()))); + return httpState; + } + + /** + * @param target + * @return + */ + private HostConfiguration getHostConfig(TransferTarget target) + { + String requiredProtocol = target.getEndpointProtocol(); + if (requiredProtocol == null) + { + throw new TransferException(MSG_UNSUPPORTED_PROTOCOL, new Object[] {target.getEndpointProtocol()}); + } + + Protocol protocol = protocolMap.get(requiredProtocol.toLowerCase().trim()); + if (protocol == null) { + log.error("Unsupported protocol: " + target.getEndpointProtocol()); + throw new TransferException(MSG_UNSUPPORTED_PROTOCOL, new Object[] {target.getEndpointProtocol()}); + } + + HostConfiguration hostConfig = new HostConfiguration(); + hostConfig.setHost(target.getEndpointHost(), target.getEndpointPort(), protocol); + return hostConfig; + } + + public Transfer begin(TransferTarget target) throws TransferException + { + HttpMethod beginRequest = new PostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + beginRequest.setPath(target.getEndpointPath() + "/begin"); + try + { + int responseStatus = httpClient.executeMethod(hostConfig, beginRequest, httpState); + checkResponseStatus("begin", responseStatus, beginRequest); + //If we get here then we've received a 200 response + //We're expecting the transfer id encoded in a JSON object... + JSONObject response = new JSONObject(beginRequest.getResponseBodyAsString()); + String transferId = response.getString("transferId"); + if(log.isDebugEnabled()) + { + log.debug("begin transfer transferId:" + transferId +", target:" + target); + } + Transfer transfer = new Transfer(); + transfer.setTransferId(transferId); + transfer.setTransferTarget(target); + return transfer; + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[] {"begin", target.toString(), e.toString()}, e); + } + } + finally + { + beginRequest.releaseConnection(); + } + } + + public DeltaList sendManifest(Transfer transfer, File manifest) throws TransferException + { + TransferTarget target = transfer.getTransferTarget(); + PostMethod postSnapshotRequest = new PostMethod(); + MultipartRequestEntity requestEntity; + + if(log.isDebugEnabled()) + { + log.debug("does manifest exist? " + manifest.exists()); + log.debug("sendManifest file : " + manifest.getAbsoluteFile()); + } + + + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + try + { + postSnapshotRequest.setPath(target.getEndpointPath() + "/post-snapshot"); + + //Put the transferId on the query string + postSnapshotRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + + //TODO encapsulate the name of the manifest part + //And add the manifest file as a "part" + Part file = new FilePart(TransferCommons.PART_NAME_MANIFEST, manifest); + requestEntity = new MultipartRequestEntity(new Part[] {file}, postSnapshotRequest.getParams()); + postSnapshotRequest.setRequestEntity(requestEntity); + + int responseStatus = httpClient.executeMethod(hostConfig, postSnapshotRequest, httpState); + checkResponseStatus("sendManifest", responseStatus, postSnapshotRequest); + return null; + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"sendManifest", target.toString(), e.toString()}, e); + } + } + finally + { + postSnapshotRequest.releaseConnection(); + } + } + + public void abort(Transfer transfer) throws TransferException + { + TransferTarget target = transfer.getTransferTarget(); + HttpMethod abortRequest = new PostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + abortRequest.setPath(target.getEndpointPath() + "/abort"); + //Put the transferId on the query string + abortRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + + try + { + int responseStatus = httpClient.executeMethod(hostConfig, abortRequest, httpState); + checkResponseStatus("abort", responseStatus, abortRequest); + //If we get here then we've received a 200 response + //We're expecting the transfer id encoded in a JSON object... + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"abort", target.toString(), e.toString()}, e); + } + } + finally + { + abortRequest.releaseConnection(); + } + } + + public void commit(Transfer transfer) throws TransferException + { + TransferTarget target = transfer.getTransferTarget(); + HttpMethod commitRequest = new PostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + commitRequest.setPath(target.getEndpointPath() + "/commit"); + //Put the transferId on the query string + commitRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + try + { + int responseStatus = httpClient.executeMethod(hostConfig, commitRequest, httpState); + checkResponseStatus("commit", responseStatus, commitRequest); + //If we get here then we've received a 200 response + //We're expecting the transfer id encoded in a JSON object... + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.error(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"commit", target.toString(), e.toString()}, e); + } + } + finally + { + commitRequest.releaseConnection(); + } + } + + public void prepare(Transfer transfer) throws TransferException + { + TransferTarget target = transfer.getTransferTarget(); + HttpMethod prepareRequest = new PostMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + prepareRequest.setPath(target.getEndpointPath() + "/prepare"); + //Put the transferId on the query string + prepareRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + try + { + int responseStatus = httpClient.executeMethod(hostConfig, prepareRequest, httpState); + checkResponseStatus("prepare", responseStatus, prepareRequest); + //If we get here then we've received a 200 response + //We're expecting the transfer id encoded in a JSON object... + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"prepare", target.toString(), e.toString()}, e); + } + } + finally + { + prepareRequest.releaseConnection(); + } + } + + /** + * + */ + public void sendContent(Transfer transfer, Set data) throws TransferException + { + if(log.isDebugEnabled()) + { + log.debug("send content to transfer:" + transfer); + } + + TransferTarget target = transfer.getTransferTarget(); + PostMethod postContentRequest = new PostMethod(); + + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + try + { + postContentRequest.setPath(target.getEndpointPath() + "/post-content"); + //Put the transferId on the query string + postContentRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + + //Put the transferId on the query string + postContentRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + + Part[] parts = new Part[data.size()]; + + int index = 0; + for(ContentData content : data) + { + // TODO Encapsulate the URL to FileName algorithm + String contentUrl = content.getContentUrl(); + String fileName = TransferCommons.URLToPartName(contentUrl); + log.debug("content partName: " + fileName); + + parts[index++] = new ContentDataPart(getContentService(), fileName, content); + } + + MultipartRequestEntity requestEntity = new MultipartRequestEntity(parts, postContentRequest.getParams()); + postContentRequest.setRequestEntity(requestEntity); + + int responseStatus = httpClient.executeMethod(hostConfig, postContentRequest, httpState); + checkResponseStatus("sendContent", responseStatus, postContentRequest); + + if(log.isDebugEnabled()) + { + log.debug("sent content"); + } + + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"sendContent", target.toString(), e.toString()}, e); + } + } + finally + { + postContentRequest.releaseConnection(); + } + } // end of sendContent + + + /** + * + */ + public Set getMessages(Transfer transfer) + { + // TODO How to signal last message ? return null rather than empty + Set messages = new HashSet(); + + TransferTarget target = transfer.getTransferTarget(); + HttpMethod messagesRequest = new GetMethod(); + try + { + HostConfiguration hostConfig = getHostConfig(target); + HttpState httpState = getHttpState(target); + + messagesRequest.setPath(target.getEndpointPath() + "/messages"); + //Put the transferId on the query string + messagesRequest.setQueryString( + new NameValuePair[] {new NameValuePair("transferId", transfer.getTransferId())}); + + try + { + int responseStatus = httpClient.executeMethod(hostConfig, messagesRequest, httpState); + checkResponseStatus("getMessages", responseStatus, messagesRequest); + //If we get here then we've received a 200 response + //We're expecting the transfer id encoded in a JSON object... + + JSONObject lookupResult = new JSONObject(messagesRequest.getResponseBodyAsString()); + JSONArray data = lookupResult.getJSONArray("data"); + + for(int i = 0; i < data.length(); i++) + { + JSONObject obj = data.getJSONObject(i); + String message = obj.getString("message"); + //TODO Need some sort of TransferMessage impl +// messages.add(new TransferMessageImpl()); + } + + return messages; + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String error = "Failed to execute HTTP request to target"; + log.debug(error, e); + throw new TransferException(MSG_HTTP_REQUEST_FAILED, new Object[]{"getMessages", target.toString(), e.toString()}, e); + } + } + finally + { + messagesRequest.releaseConnection(); + } + } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public ContentService getContentService() + { + return contentService; + } + + +} // end of class diff --git a/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImplTest.java b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImplTest.java new file mode 100644 index 0000000000..b2b965dba4 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/HttpClientTransmitterImplTest.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import junit.framework.TestCase; + +import org.alfresco.service.cmr.transfer.TransferException; +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.HostConfiguration; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpState; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.params.HttpConnectionParams; +import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; +import org.mockito.ArgumentCaptor; + +/** + * Unit test for HttpClientTransmitterImpl + * + * @author Brian Remmington + */ +public class HttpClientTransmitterImplTest extends TestCase +{ + + + private static final String TARGET_HOST = "my.testhost.com"; + private static final String HTTP_PROTOCOL = "HTTP"; + private static final String HTTPS_PROTOCOL = "HTTPS"; + private static final String TRANSFER_SERVICE_PATH = "/api/transfer"; + private static final int HTTP_PORT = 80; + private static final int HTTPS_PORT = 443; + private static final String TARGET_USERNAME = "transferuser"; + private static final char[] TARGET_PASSWORD = "password".toCharArray(); + + private HttpClientTransmitterImpl transmitter; + private HttpClient mockedHttpClient; + private TransferTargetImpl target; + + /* (non-Javadoc) + * @see junit.framework.TestCase#setUp() + */ + @Override + protected void setUp() throws Exception + { + super.setUp(); + + this.transmitter = new HttpClientTransmitterImpl(); + this.mockedHttpClient = mock(HttpClient.class); + transmitter.setHttpClient(mockedHttpClient); + + this.target = new TransferTargetImpl(); + target.setEndpointHost(TARGET_HOST); + target.setEndpointProtocol(HTTP_PROTOCOL); + target.setEndpointPath(TRANSFER_SERVICE_PATH); + target.setEndpointPort(HTTP_PORT); + target.setUsername(TARGET_USERNAME); + target.setPassword(TARGET_PASSWORD); + } + + /** + * Test create target. + * + * @throws Exception + */ + public void testSuccessfulVerifyTargetOverHttp() throws Exception + { + //Stub HttpClient so that executeMethod returns a 200 response + when(mockedHttpClient.executeMethod(any(HostConfiguration.class), any(HttpMethod.class), + any(HttpState.class))).thenReturn(200); + + //Call verifyTarget + transmitter.verifyTarget(target); + + ArgumentCaptor hostConfig = ArgumentCaptor.forClass(HostConfiguration.class); + ArgumentCaptor httpMethod = ArgumentCaptor.forClass(HttpMethod.class); + ArgumentCaptor httpState = ArgumentCaptor.forClass(HttpState.class); + + verify(mockedHttpClient).executeMethod(hostConfig.capture(), httpMethod.capture(), httpState.capture()); + + assertTrue("post method", httpMethod.getValue() instanceof PostMethod); + assertEquals("host name", TARGET_HOST, hostConfig.getValue().getHost()); + assertEquals("port", HTTP_PORT, hostConfig.getValue().getPort()); + assertEquals("protocol", HTTP_PROTOCOL.toLowerCase(), + hostConfig.getValue().getProtocol().getScheme().toLowerCase()); + assertEquals("path", TRANSFER_SERVICE_PATH + "/test", httpMethod.getValue().getPath()); + } + + public void testSuccessfulVerifyTargetOverHttps() throws Exception + { + + //Stub HttpClient so that executeMethod returns a 200 response + when(mockedHttpClient.executeMethod(any(HostConfiguration.class), any(HttpMethod.class), + any(HttpState.class))).thenReturn(200); + + target.setEndpointProtocol(HTTPS_PROTOCOL); + target.setEndpointPort(HTTPS_PORT); + + //Call verifyTarget + transmitter.verifyTarget(target); + + ArgumentCaptor hostConfig = ArgumentCaptor.forClass(HostConfiguration.class); + ArgumentCaptor httpMethod = ArgumentCaptor.forClass(HttpMethod.class); + ArgumentCaptor httpState = ArgumentCaptor.forClass(HttpState.class); + + verify(mockedHttpClient).executeMethod(hostConfig.capture(), httpMethod.capture(), httpState.capture()); + + assertEquals("port", HTTPS_PORT, hostConfig.getValue().getPort()); + assertTrue("socket factory", + hostConfig.getValue().getProtocol().getSocketFactory() instanceof SecureProtocolSocketFactory); + assertEquals("protocol", HTTPS_PROTOCOL.toLowerCase(), + hostConfig.getValue().getProtocol().getScheme().toLowerCase()); + } + + public void testHttpsVerifyTargetWithCustomSocketFactory() throws Exception + { + //Override the default SSL socket factory with our own custom one... + CustomSocketFactory socketFactory = new CustomSocketFactory(); + transmitter.setHttpsSocketFactory(socketFactory); + + target.setEndpointProtocol(HTTPS_PROTOCOL); + target.setEndpointPort(HTTPS_PORT); + + //Stub HttpClient so that executeMethod returns a 200 response + when(mockedHttpClient.executeMethod(any(HostConfiguration.class), any(HttpMethod.class), + any(HttpState.class))).thenReturn(200); + + //Call verifyTarget + transmitter.verifyTarget(target); + + ArgumentCaptor hostConfig = ArgumentCaptor.forClass(HostConfiguration.class); + ArgumentCaptor httpMethod = ArgumentCaptor.forClass(HttpMethod.class); + ArgumentCaptor httpState = ArgumentCaptor.forClass(HttpState.class); + + verify(mockedHttpClient).executeMethod(hostConfig.capture(), httpMethod.capture(), httpState.capture()); + + assertEquals("port", HTTPS_PORT, hostConfig.getValue().getPort()); + //test that the socket factory passed to HttpClient is our custom one (intentional use of '==') + assertTrue("socket factory", hostConfig.getValue().getProtocol().getSocketFactory() == socketFactory); + assertEquals("protocol", HTTPS_PROTOCOL.toLowerCase(), + hostConfig.getValue().getProtocol().getScheme().toLowerCase()); + } + + + public void testVerifyTargetWithInvalidProtocol() throws Exception + { + target.setEndpointProtocol("invalidprotocol"); + try + { + transmitter.verifyTarget(target); + fail("invalid protocol"); + } + catch(TransferException ex) + { + //expected + } + } + + public void testUnauthorisedVerifyTarget() throws Exception + { + //Stub HttpClient so that executeMethod returns a 401 response + when(mockedHttpClient.executeMethod(any(HostConfiguration.class), any(HttpMethod.class), + any(HttpState.class))).thenReturn(401); + + try + { + transmitter.verifyTarget(target); + } + catch (TransferException ex) + { + //expected + } + } + + private static class CustomSocketFactory implements SecureProtocolSocketFactory + { + + /* (non-Javadoc) + * @see org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory#createSocket(java.net.Socket, java.lang.String, int, boolean) + */ + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, + UnknownHostException + { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see org.apache.commons.httpclient.protocol.ProtocolSocketFactory#createSocket(java.lang.String, int) + */ + public Socket createSocket(String host, int port) throws IOException, UnknownHostException + { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see org.apache.commons.httpclient.protocol.ProtocolSocketFactory#createSocket(java.lang.String, int, java.net.InetAddress, int) + */ + public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException, + UnknownHostException + { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see org.apache.commons.httpclient.protocol.ProtocolSocketFactory#createSocket(java.lang.String, int, java.net.InetAddress, int, org.apache.commons.httpclient.params.HttpConnectionParams) + */ + public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, + HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException + { + // TODO Auto-generated method stub + return null; + } + + } +} diff --git a/source/java/org/alfresco/repo/transfer/ManifestProcessorFactory.java b/source/java/org/alfresco/repo/transfer/ManifestProcessorFactory.java new file mode 100644 index 0000000000..26115ebd01 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/ManifestProcessorFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.util.List; + +import org.alfresco.repo.transfer.manifest.TransferManifestProcessor; +import org.alfresco.service.cmr.transfer.TransferReceiver; + +/** + * @author brian + * + */ +public interface ManifestProcessorFactory +{ + List getCommitProcessors(TransferReceiver receiver, String transferId); +} diff --git a/source/java/org/alfresco/repo/transfer/NodeCrawlerTest.java b/source/java/org/alfresco/repo/transfer/NodeCrawlerTest.java new file mode 100644 index 0000000000..6e5708a0fc --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/NodeCrawlerTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseAlfrescoSpringTest; +import org.alfresco.util.GUID; + +/** + * Unit test for RepoTransferReceiverImpl + * + * @author Brian Remmington + */ +public class NodeCrawlerTest extends BaseAlfrescoSpringTest +{ + private ServiceRegistry serviceRegistry; + private NodeRef companyHome; + + /** + * Called during the transaction setup + */ + @SuppressWarnings("deprecation") + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + + // Get the required services + this.nodeService = (NodeService) this.getApplicationContext().getBean("NodeService"); + this.serviceRegistry = (ServiceRegistry) this.getApplicationContext().getBean("ServiceRegistry"); + ResultSet rs = serviceRegistry.getSearchService().query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, + SearchService.LANGUAGE_XPATH, "/app:company_home"); + if (rs.length() == 0) + { + fail("Could not find company home"); + } + companyHome = rs.getNodeRef(0); + } + + public void testContentClassFilter() throws Exception + { + NodeRef node1 = makeNode(companyHome, ContentModel.TYPE_BASE); + NodeRef node2 = makeNode(companyHome, ContentModel.TYPE_CONTENT); + NodeRef node3 = makeNode(companyHome, ContentModel.TYPE_FOLDER); + NodeRef node4 = makeNode(companyHome, ContentModel.TYPE_CONTENT); + NodeRef node5 = makeNode(companyHome, ContentModel.TYPE_THUMBNAIL); + + nodeService.addAspect(node4, ContentModel.ASPECT_REFERENCEABLE, null); + + ContentClassFilter contentFilter = new ContentClassFilter(serviceRegistry); + contentFilter.setContentClasses(ContentModel.TYPE_BASE); + + assertTrue(contentFilter.accept(node1)); + assertTrue(contentFilter.accept(node2)); + assertTrue(contentFilter.accept(node3)); + + contentFilter.setDirectOnly(true); + assertTrue(contentFilter.accept(node1)); + assertFalse(contentFilter.accept(node2)); + assertFalse(contentFilter.accept(node3)); + + contentFilter.setContentClasses(ContentModel.TYPE_BASE, ContentModel.ASPECT_REFERENCEABLE); + assertTrue(contentFilter.accept(node4)); + + contentFilter.setExclude(true); + contentFilter.setDirectOnly(false); + contentFilter.setContentClasses(ContentModel.TYPE_CONTENT); + assertTrue(contentFilter.accept(node1)); + assertFalse(contentFilter.accept(node2)); + assertTrue(contentFilter.accept(node3)); + assertFalse(contentFilter.accept(node5)); + + contentFilter.setDirectOnly(true); + assertTrue(contentFilter.accept(node1)); + assertFalse(contentFilter.accept(node2)); + assertTrue(contentFilter.accept(node3)); + assertTrue(contentFilter.accept(node5)); + + } + + public void testChildAssociationFinder() + { + makeNode(companyHome, ContentModel.TYPE_BASE); + makeNode(companyHome, ContentModel.TYPE_CONTENT); + makeNode(companyHome, ContentModel.TYPE_CONTENT); + makeNode(companyHome, ContentModel.TYPE_CONTENT); + makeNode(companyHome, ContentModel.TYPE_CONTENT); + makeNode(companyHome, ContentModel.TYPE_CONTENT); + makeNode(companyHome, ContentModel.TYPE_CONTENT); + NodeRef node8 = makeNode(companyHome, ContentModel.TYPE_FOLDER); + makeNode(node8, ContentModel.TYPE_FOLDER); + NodeRef node10 = makeNode(node8, ContentModel.TYPE_FOLDER); + makeNode(node10, ContentModel.TYPE_FOLDER); + NodeRef node12 = makeNode(node10, ContentModel.TYPE_FOLDER); + NodeRef node13 = makeNode(node12, ContentModel.TYPE_FOLDER); + makeNode(node13, ContentModel.TYPE_CONTENT); + NodeRef node15 = makeNode(node13, ContentModel.TYPE_THUMBNAIL); + + nodeService.addAspect(node8, ContentModel.ASPECT_THUMBNAILED, null); + nodeService.addChild(node8, node15, ContentModel.ASSOC_THUMBNAILS, QName.createQName( + NamespaceService.APP_MODEL_1_0_URI, "temp")); + + ChildAssociatedNodeFinder nodeFinder = new ChildAssociatedNodeFinder(serviceRegistry); + nodeFinder.setAssociationTypes(ContentModel.ASSOC_CONTAINS); + + Set results = nodeFinder.findFrom(node8); + assertEquals(2, results.size()); + + nodeFinder.setAssociationTypes(ContentModel.ASSOC_THUMBNAILS); + results = nodeFinder.findFrom(node8); + assertEquals(1, results.size()); + assertEquals(node15, new ArrayList(results).get(0)); + } + + public void testCrawler() + { + NodeRef node8 = makeNode(companyHome, ContentModel.TYPE_FOLDER); + NodeRef node9 = makeNode(node8, ContentModel.TYPE_FOLDER); + NodeRef node10 = makeNode(node8, ContentModel.TYPE_FOLDER); + NodeRef node11 = makeNode(node10, ContentModel.TYPE_FOLDER); + NodeRef node12 = makeNode(node10, ContentModel.TYPE_FOLDER); + NodeRef node13 = makeNode(node12, ContentModel.TYPE_FOLDER); + + makeNode(node10, ContentModel.TYPE_BASE); + makeNode(node13, ContentModel.TYPE_CONTENT); + makeNode(node10, ContentModel.TYPE_CONTENT); + makeNode(node11, ContentModel.TYPE_CONTENT); + makeNode(node8, ContentModel.TYPE_CONTENT); + makeNode(node8, ContentModel.TYPE_CONTENT); + makeNode(node9, ContentModel.TYPE_CONTENT); + makeNode(node13, ContentModel.TYPE_CONTENT); + NodeRef node15 = makeNode(node13, ContentModel.TYPE_THUMBNAIL); + + nodeService.addAspect(node8, ContentModel.ASPECT_THUMBNAILED, null); + nodeService.addChild(node8, node15, ContentModel.ASSOC_THUMBNAILS, QName.createQName( + NamespaceService.APP_MODEL_1_0_URI, "temp")); + + StandardNodeCrawlerImpl crawler = new StandardNodeCrawlerImpl(serviceRegistry); + crawler.setNodeFinders(new ChildAssociatedNodeFinder(ContentModel.ASSOC_CONTAINS)); + + Set crawledNodes = crawler.crawl(node8); + assertEquals(15, crawledNodes.size()); + + crawler.setNodeFilters(new ContentClassFilter(ContentModel.TYPE_FOLDER)); + crawledNodes = crawler.crawl(node8); + assertEquals(6, crawledNodes.size()); + } + + /** + * @param companyHome2 + * @param nodeType + * @return + */ + private NodeRef makeNode(NodeRef parent, QName nodeType) + { + String uuid = GUID.generate(); + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, uuid); + ChildAssociationRef assoc = nodeService.createNode(parent, ContentModel.ASSOC_CONTAINS, QName.createQName( + NamespaceService.APP_MODEL_1_0_URI, uuid), nodeType, props); + return assoc.getChildRef(); + } + +} diff --git a/source/java/org/alfresco/repo/transfer/PathHelper.java b/source/java/org/alfresco/repo/transfer/PathHelper.java new file mode 100644 index 0000000000..4c209fda4b --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/PathHelper.java @@ -0,0 +1,61 @@ +package org.alfresco.repo.transfer; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.util.ISO9075; + +public class PathHelper +{ + /** + * Converts a String representation of a path to a Path + * + * e.g "/{http://www.alfresco.org/model/application/1.0}company_home/{http://www.alfresco.org/model/application/1.0}dictionary/{http://www.alfresco.org/model/application/1.0}transfers/{http://www.alfresco.org/model/content/1.0}default/{http://www.alfresco.org/model/transfer/1.0}snapshotMe"; + * @param value the string representation of the path. + * @return Path + */ + public static Path stringToPath(String value) + { + Path path = new Path(); + + // pattern for QName e.g. /{stuff}stuff + + Pattern pattern = Pattern.compile("/\\{[a-zA-Z:./0-9]*\\}[A-Z0-9a-z_ ]*"); + Matcher matcher = pattern.matcher(value); + + // This is the root node + path.append(new SimplePathElement("/")); + + while ( matcher.find() ) + { + String group = matcher.group(); + final String val = ISO9075.decode(group.substring(1)); + path.append(new SimplePathElement(val)); + } + + return path; + } + + + private static class SimplePathElement extends Path.Element { + private static final long serialVersionUID = -5243552616345217924L; + private String elementString; + + public SimplePathElement(String elementString) + { + this.elementString = elementString; + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.repository.Path.Element#getElementString() + */ + @Override + public String getElementString() + { + return elementString; + } + + } + +} diff --git a/source/java/org/alfresco/repo/transfer/PeerAssociatedNodeFinder.java b/source/java/org/alfresco/repo/transfer/PeerAssociatedNodeFinder.java new file mode 100644 index 0000000000..0fca376e02 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/PeerAssociatedNodeFinder.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.transfer.NodeFinder; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.ParameterCheck; + +/** + * @author brian + * + */ +public class PeerAssociatedNodeFinder implements NodeFinder +{ + private Set suppliedAssociationTypes = new HashSet(); + private boolean exclude = false; + private boolean initialised = false; + private List peerAssociationTypes = new ArrayList(); + private ServiceRegistry serviceRegistry; + + public PeerAssociatedNodeFinder() + { + } + + public PeerAssociatedNodeFinder(Collection associationTypeNames) + { + setAssociationTypes(associationTypeNames); + } + + public PeerAssociatedNodeFinder(QName... associationTypeNames) + { + setAssociationTypes(associationTypeNames); + } + + public PeerAssociatedNodeFinder(Collection associationTypeNames, boolean exclude) + { + setAssociationTypes(associationTypeNames); + this.exclude = exclude; + } + + public PeerAssociatedNodeFinder(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + public PeerAssociatedNodeFinder(ServiceRegistry serviceRegistry, Collection associationTypeNames) + { + this(serviceRegistry); + setAssociationTypes(associationTypeNames); + } + + public PeerAssociatedNodeFinder(ServiceRegistry serviceRegistry, QName... associationTypeNames) + { + this(serviceRegistry); + setAssociationTypes(associationTypeNames); + } + + public PeerAssociatedNodeFinder(ServiceRegistry serviceRegistry, Collection associationTypeNames, boolean exclude) + { + this(serviceRegistry); + setAssociationTypes(associationTypeNames); + this.exclude = exclude; + } + + /** + * @param exclude + * Set to true to exclude the specified association types, and false to include only the specified + * association types. + */ + public void setExclude(boolean exclude) + { + this.exclude = exclude; + } + + public void setAssociationTypes(QName... associationTypes) + { + setAssociationTypes(Arrays.asList(associationTypes)); + } + + public void setAssociationTypes(Collection associationTypes) + { + suppliedAssociationTypes = new HashSet(associationTypes); + initialised = false; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.service.cmr.transfer.NodeFinder#findFrom(org.alfresco.service.cmr.repository.NodeRef, + * org.alfresco.service.ServiceRegistry) + */ + public Set findFrom(NodeRef thisNode) + { + if (!initialised) + { + init(); + } + if (exclude) + { + return processExcludedSet(thisNode); + } + else + { + return processIncludedSet(thisNode); + } + } + + /** + * @param thisNode + * @param serviceRegistry + * @return + */ + private Set processExcludedSet(NodeRef thisNode) + { + Set results = new HashSet(89); + NodeService nodeService = serviceRegistry.getNodeService(); + + // Find any peer nodes (filtering as necessary) + List targets = nodeService.getTargetAssocs(thisNode, RegexQNamePattern.MATCH_ALL); + boolean filterPeers = !peerAssociationTypes.isEmpty(); + for (AssociationRef target : targets) + { + if (!filterPeers || !peerAssociationTypes.contains(target.getTypeQName())) + { + results.add(target.getTargetRef()); + } + } + return results; + } + + private Set processIncludedSet(NodeRef startingNode) + { + NodeService nodeService = serviceRegistry.getNodeService(); + Set foundNodes = new HashSet(89); + for (QName assocType : peerAssociationTypes) + { + List targets = nodeService.getTargetAssocs(startingNode, assocType); + for (AssociationRef target : targets) + { + foundNodes.add(target.getTargetRef()); + } + } + return foundNodes; + } + + public void init() + { + ParameterCheck.mandatory("serviceRegistry", serviceRegistry); + DictionaryService dictionaryService = serviceRegistry.getDictionaryService(); + peerAssociationTypes.clear(); + for (QName associationType : suppliedAssociationTypes) + { + AssociationDefinition assocDef = dictionaryService.getAssociation(associationType); + if (assocDef != null && !assocDef.isChild()) + { + peerAssociationTypes.add(associationType); + } + } + initialised = true; + } + + /** + * @param serviceRegistry + * the serviceRegistry to set + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/RepoPrimaryManifestProcessorImpl.java b/source/java/org/alfresco/repo/transfer/RepoPrimaryManifestProcessorImpl.java new file mode 100644 index 0000000000..2309e14c8a --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/RepoPrimaryManifestProcessorImpl.java @@ -0,0 +1,612 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.io.File; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.transfer.CorrespondingNodeResolver.ResolvedParentChildPair; +import org.alfresco.repo.transfer.manifest.TransferManifestDeletedNode; +import org.alfresco.repo.transfer.manifest.TransferManifestHeader; +import org.alfresco.repo.transfer.manifest.TransferManifestNode; +import org.alfresco.repo.transfer.manifest.TransferManifestNormalNode; +import org.alfresco.repo.transfer.manifest.TransferManifestProcessor; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.transfer.TransferReceiver; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author brian + * + */ +public class RepoPrimaryManifestProcessorImpl implements TransferManifestProcessor +{ + private static final Log log = LogFactory.getLog(RepoPrimaryManifestProcessorImpl.class); + + private static final String MSG_NO_PRIMARY_PARENT_SUPPLIED = "transfer_service.receiver.no_primary_parent_supplied"; + private static final String MSG_ORPHANS_EXIST = "transfer_service.receiver.orphans_exist"; + private static final String MSG_REFERENCED_CONTENT_FILE_MISSING = "transfer_service.receiver.content_file_missing"; + + protected static final Set DEFAULT_LOCAL_PROPERTIES = new HashSet(); + + static + { + DEFAULT_LOCAL_PROPERTIES.add(ContentModel.PROP_STORE_IDENTIFIER); + DEFAULT_LOCAL_PROPERTIES.add(ContentModel.PROP_STORE_NAME); + DEFAULT_LOCAL_PROPERTIES.add(ContentModel.PROP_STORE_PROTOCOL); + DEFAULT_LOCAL_PROPERTIES.add(ContentModel.PROP_NODE_DBID); + DEFAULT_LOCAL_PROPERTIES.add(ContentModel.PROP_NODE_REF); + DEFAULT_LOCAL_PROPERTIES.add(ContentModel.PROP_NODE_UUID); + } + + private TransferReceiver receiver; + private String transferId; + private NodeService nodeService; + private ContentService contentService; + private CorrespondingNodeResolver nodeResolver; + + private Map> orphans = new HashMap>(89); + + /** + * @param transferId + */ + public RepoPrimaryManifestProcessorImpl(TransferReceiver receiver, String transferId) + { + this.receiver = receiver; + this.transferId = transferId; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.transfer.manifest.TransferManifestProcessor#endTransferManifest() + */ + public void endTransferManifest() + { + if (!orphans.isEmpty()) + { + error(MSG_ORPHANS_EXIST); + } + } + + /** + * + */ + public void processTransferManifestNode(TransferManifestDeletedNode node) + { + // This is a deleted node. First we need to check whether it has already been deleted in this repo + // too by looking in the local archive store. If we find it then we need not do anything. + // If we can't find it in our archive store then we'll see if we can find a corresponding node in the + // store in which its old parent lives. + // If we can find a corresponding node then we'll delete it. + // If we can't find a corresponding node then we'll do nothing. + + if (!nodeService.exists(node.getNodeRef())) + { + // It's not in our archive store. Check to see if we can find it in its original store... + ChildAssociationRef origPrimaryParent = node.getPrimaryParentAssoc(); + NodeRef origNodeRef = new NodeRef(origPrimaryParent.getParentRef().getStoreRef(), node.getNodeRef().getId()); + + CorrespondingNodeResolver.ResolvedParentChildPair resolvedNodes = nodeResolver.resolveCorrespondingNode( + origNodeRef, origPrimaryParent, node.getParentPath()); + + // Does a corresponding node exist in this repo? + if (resolvedNodes.resolvedChild != null) + { + // Yes, it does. Delete it. + if (log.isDebugEnabled()) + { + log.debug("Incoming deleted noderef " + node.getNodeRef() + + " has been resolved to existing local noderef " + resolvedNodes.resolvedChild + + " - deleting"); + } + nodeService.deleteNode(resolvedNodes.resolvedChild); + } + else + { + if (log.isDebugEnabled()) + { + log.debug("Incoming deleted noderef has no corresponding local noderef: " + node.getNodeRef() + + " - ignoring"); + } + } + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.transfer.manifest.TransferManifestProcessor#processTransferManifestNode(org.alfresco.repo.transfer + * .manifest.TransferManifestNode) + */ + public void processTransferManifestNode(TransferManifestNormalNode node) + { + try + { + if (log.isDebugEnabled()) + { + log.debug("Processing node with incoming noderef of " + node.getNodeRef()); + } + ChildAssociationRef primaryParentAssoc = getPrimaryParent(node); + if (primaryParentAssoc == null) + { + error(node, MSG_NO_PRIMARY_PARENT_SUPPLIED); + } + + CorrespondingNodeResolver.ResolvedParentChildPair resolvedNodes = nodeResolver.resolveCorrespondingNode( + node.getNodeRef(), primaryParentAssoc, node.getParentPath()); + + // Does a corresponding node exist in this repo? + if (resolvedNodes.resolvedChild != null) + { + // Yes, it does. Update it. + if (log.isDebugEnabled()) + { + log.debug("Incoming noderef " + node.getNodeRef() + + " has been resolved to existing local noderef " + resolvedNodes.resolvedChild); + } + update(node, resolvedNodes, primaryParentAssoc); + } + else + { + // No, there is no corresponding node. Worth just quickly checking the archive store... + NodeRef archiveNodeRef = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, node.getNodeRef().getId()); + if (nodeService.exists(archiveNodeRef)) + { + // We have found a node in the archive store that has the same UUID as the one that we've + // been sent. We'll restore that archived node to a temporary location and then try + // processing this node again + if (log.isInfoEnabled()) + { + log.info("Located an archived node with UUID matching transferred node: " + archiveNodeRef); + log.info("Attempting to restore " + archiveNodeRef); + } + ChildAssociationRef tempLocation = getTemporaryLocation(node.getNodeRef()); + NodeRef restoredNodeRef = nodeService.restoreNode(archiveNodeRef, tempLocation.getParentRef(), + tempLocation.getTypeQName(), tempLocation.getQName()); + if (log.isInfoEnabled()) + { + log.info("Successfully restored node as " + restoredNodeRef + " - retrying transferred node"); + } + processTransferManifestNode(node); + return; + } + + if (log.isDebugEnabled()) + { + log.debug("Incoming noderef has no corresponding local noderef: " + node.getNodeRef()); + } + create(node, resolvedNodes, primaryParentAssoc); + } + + } + catch (TransferProcessingException ex) + { + log.error("transfer processing exception" + ex.toString(), ex); + //TODO MER BUGBUG - What to do here? probably can't just swallow it + // does this mean that the manifest is stuffed? + } + } + + /** + * Given the node ref, this method constructs the appropriate ChildAssociationRef that would place this node in the + * transfer's temporary folder. Useful when handling orphans. + * + * @param nodeRef + * @return + */ + private ChildAssociationRef getTemporaryLocation(NodeRef nodeRef) + { + NodeRef parentNodeRef = receiver.getTempFolder(transferId); + QName parentAssocType = ContentModel.ASSOC_CONTAINS; + QName parentAssocName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, nodeRef.getId()); + return new ChildAssociationRef(parentAssocType, parentNodeRef, parentAssocName, nodeRef, true, -1); + } + + /** + * + * @param node + * @param resolvedNodes + * @param primaryParentAssoc + */ + private void create(TransferManifestNormalNode node, ResolvedParentChildPair resolvedNodes, + ChildAssociationRef primaryParentAssoc) + { + log.info("Creating new node with noderef " + node.getNodeRef()); + QName parentAssocType = primaryParentAssoc.getTypeQName(); + QName parentAssocName = primaryParentAssoc.getQName(); + NodeRef parentNodeRef = resolvedNodes.resolvedParent; + if (parentNodeRef == null) + { + if (log.isDebugEnabled()) + { + log.debug("Unable to resolve parent for inbound noderef " + node.getNodeRef() + + ".\n Supplied parent noderef is " + primaryParentAssoc.getParentRef() + + ".\n Supplied parent path is " + node.getParentPath().toString()); + } + // We can't find the node's parent. + // We'll store the node in a temporary location and record it for later processing + ChildAssociationRef tempLocation = getTemporaryLocation(node.getNodeRef()); + parentNodeRef = tempLocation.getParentRef(); + parentAssocType = tempLocation.getTypeQName(); + parentAssocName = tempLocation.getQName(); + log.info("Recording orphaned transfer node: " + node.getNodeRef()); + storeOrphanNode(primaryParentAssoc); + } + // We now know that this is a new node, and we have found the appropriate parent node in the + // local repository. + log.info("Resolved parent node to " + parentNodeRef); + + // We need to process content properties separately. + // First, create a shallow copy of the supplied property map... + Map props = new HashMap(node.getProperties()); + + // Split out the content properties and sanitise the others + Map contentProps = processProperties(null, props, true); + + // Create the corresponding node... + ChildAssociationRef newNode = nodeService.createNode(parentNodeRef, parentAssocType, parentAssocName, + node.getType(), props); + + if (log.isDebugEnabled()) + { + log.debug("Created new node (" + newNode.getChildRef() + ") parented by node " + newNode.getParentRef()); + } + + // Deal with the content properties + writeContent(newNode.getChildRef(), contentProps); + + // Apply any aspects that are needed but haven't automatically been applied + Set aspects = new HashSet(node.getAspects()); + aspects.removeAll(nodeService.getAspects(newNode.getChildRef())); + for (QName aspect : aspects) + { + nodeService.addAspect(newNode.getChildRef(), aspect, null); + } + + // Is the node that we've just added the parent of any orphans that we've found earlier? + List orphansToClaim = orphans.get(newNode.getChildRef()); + if (orphansToClaim != null) + { + // Yes, it is... + for (ChildAssociationRef orphan : orphansToClaim) + { + nodeService.moveNode(orphan.getChildRef(), orphan.getParentRef(), orphan.getTypeQName(), + orphan.getQName()); + } + // We can now remove the record of these orphans, as their parent has been found + orphans.remove(newNode.getChildRef()); + } + } + + /** + * + * @param node + * @param resolvedNodes + * @param primaryParentAssoc + */ + private void update(TransferManifestNormalNode node, ResolvedParentChildPair resolvedNodes, + ChildAssociationRef primaryParentAssoc) + { + NodeRef nodeToUpdate = resolvedNodes.resolvedChild; + + QName parentAssocType = primaryParentAssoc.getTypeQName(); + QName parentAssocName = primaryParentAssoc.getQName(); + NodeRef parentNodeRef = resolvedNodes.resolvedParent; + if (parentNodeRef == null) + { + // We can't find the node's parent. + // We'll store the node in a temporary location and record it for later processing + ChildAssociationRef tempLocation = getTemporaryLocation(node.getNodeRef()); + parentNodeRef = tempLocation.getParentRef(); + parentAssocType = tempLocation.getTypeQName(); + parentAssocName = tempLocation.getQName(); + storeOrphanNode(primaryParentAssoc); + } + // First of all, do we need to move the node? If any aspect of the primary parent association has changed + // then the answer is "yes" + ChildAssociationRef currentParent = nodeService.getPrimaryParent(nodeToUpdate); + if (!currentParent.getParentRef().equals(parentNodeRef) || + !currentParent.getTypeQName().equals(parentAssocType) || + !currentParent.getQName().equals(parentAssocName)) + { + // Yes, we need to move the node + nodeService.moveNode(nodeToUpdate, parentNodeRef, parentAssocType, parentAssocName); + } + + log.info("Resolved parent node to " + parentNodeRef); + + if (updateNeeded(node, nodeToUpdate)) + { + + // We need to process content properties separately. + // First, create a shallow copy of the supplied property map... + Map props = new HashMap(node.getProperties()); + + // Split out the content properties and sanitise the others + Map contentProps = processProperties(nodeToUpdate, props, false); + + // Update the non-content properties + nodeService.setProperties(nodeToUpdate, props); + + // Deal with the content properties + writeContent(nodeToUpdate, contentProps); + + // Blend the aspects together + Set suppliedAspects = new HashSet(node.getAspects()); + Set existingAspects = nodeService.getAspects(nodeToUpdate); + Set aspectsToRemove = new HashSet(existingAspects); + + aspectsToRemove.removeAll(suppliedAspects); + suppliedAspects.removeAll(existingAspects); + + // Now aspectsToRemove contains the set of aspects to remove + // and suppliedAspects contains the set of aspects to add + for (QName aspect : suppliedAspects) + { + nodeService.addAspect(nodeToUpdate, aspect, null); + } + + for (QName aspect : aspectsToRemove) + { + nodeService.removeAspect(nodeToUpdate, aspect); + } + } + } + + /** + * This method takes all the received properties and separates them into two parts. The content properties are + * removed from the non-content properties such that the non-content properties remain in the "props" map and the + * content properties are returned from this method Subsequently, any properties that are to be retained from the + * local repository are copied over into the "props" map. The result of all this is that, upon return, "props" + * contains all the non-content properties that are to be written to the local repo, and "contentProps" contains all + * the content properties that are to be written to the local repo. + * + * @param nodeToUpdate + * The noderef of the existing node in the local repo that is to be updated with these properties. May be + * null, indicating that these properties are destined for a brand new local node. + * @param props + * @return A map containing the content properties from the supplied "props" map + */ + private Map processProperties(NodeRef nodeToUpdate, Map props, + boolean isNew) + { + Map contentProps = new HashMap(); + // ...and copy any supplied content properties into this new map... + for (Map.Entry propEntry : props.entrySet()) + { + if (ContentData.class.isAssignableFrom(propEntry.getValue().getClass())) + { + contentProps.put(propEntry.getKey(), propEntry.getValue()); + } + } + + // Now we can remove the content properties from amongst the other kinds of properties + // (no removeAll on a Map...) + for (QName contentPropertyName : contentProps.keySet()) + { + props.remove(contentPropertyName); + } + + if (!isNew) + { + // Finally, overlay the repo-specific properties from the existing node (if there is one) + Map existingProps = (nodeToUpdate == null) ? new HashMap() + : nodeService.getProperties(nodeToUpdate); + + for (QName localProperty : getLocalProperties()) + { + Serializable existingValue = existingProps.get(localProperty); + if (existingValue != null) + { + props.put(localProperty, existingValue); + } + else + { + props.remove(localProperty); + } + } + } + return contentProps; + } + + /** + * @param node + * @param nodeToUpdate + * @param contentProps + */ + private void writeContent(NodeRef nodeToUpdate, Map contentProps) + { + File stagingDir = receiver.getStagingFolder(transferId); + for (Map.Entry contentEntry : contentProps.entrySet()) + { + ContentData contentData = (ContentData) contentEntry.getValue(); + String contentUrl = contentData.getContentUrl(); + String fileName = contentUrl.substring(contentUrl.lastIndexOf('/') + 1); + File stagedFile = new File(stagingDir, fileName); + if (!stagedFile.exists()) + { + error(MSG_REFERENCED_CONTENT_FILE_MISSING); + } + ContentWriter writer = contentService.getWriter(nodeToUpdate, contentEntry.getKey(), true); + writer.setEncoding(contentData.getEncoding()); + writer.setMimetype(contentData.getMimetype()); + writer.setLocale(contentData.getLocale()); + writer.putContent(stagedFile); + } + } + + protected boolean updateNeeded(TransferManifestNode node, NodeRef nodeToUpdate) + { + return true; + // TODO MER - Temp commenting out. + // //Assumption: if the modified and modifier properties haven't changed, and the cm:content property + // //(if it exists) hasn't changed size then we can assume that properties don't need to be updated... + // Map suppliedProps = node.getProperties(); + // Date suppliedModifiedDate = (Date)suppliedProps.get(ContentModel.PROP_MODIFIED); + // String suppliedModifier = (String)suppliedProps.get(ContentModel.PROP_MODIFIER); + // ContentData suppliedContent = (ContentData)suppliedProps.get(ContentModel.PROP_CONTENT); + // + // Map existingProps = nodeService.getProperties(nodeToUpdate); + // Date existingModifiedDate = (Date)existingProps.get(ContentModel.PROP_MODIFIED); + // String existingModifier = (String)existingProps.get(ContentModel.PROP_MODIFIER); + // ContentData existingContent = (ContentData)existingProps.get(ContentModel.PROP_CONTENT); + // + // boolean updateNeeded = false; + // updateNeeded |= ((suppliedModifiedDate != null && !suppliedModifiedDate.equals(existingModifiedDate)) || + // (existingModifiedDate != null && !existingModifiedDate.equals(suppliedModifiedDate))); + // updateNeeded |= ((suppliedContent != null && existingContent == null) || + // (suppliedContent == null && existingContent != null) || + // (suppliedContent != null && existingContent != null && suppliedContent.getSize() != + // existingContent.getSize())); + // updateNeeded |= ((suppliedModifier != null && !suppliedModifier.equals(existingModifier)) || + // (existingModifier != null && !existingModifier.equals(suppliedModifier))); + // return updateNeeded; + } + + /** + * @return + */ + protected Set getLocalProperties() + { + return DEFAULT_LOCAL_PROPERTIES; + } + + /** + * @param primaryParentAssoc + */ + private void storeOrphanNode(ChildAssociationRef primaryParentAssoc) + { + List orphansOfParent = orphans.get(primaryParentAssoc.getParentRef()); + if (orphansOfParent == null) + { + orphansOfParent = new ArrayList(); + orphans.put(primaryParentAssoc.getParentRef(), orphansOfParent); + } + orphansOfParent.add(primaryParentAssoc); + } + + /** + * @param node + * @param msgId + */ + private void error(TransferManifestNode node, String msgId) + { + TransferProcessingException ex = new TransferProcessingException(msgId); + log.error(ex.getMessage(), ex); + throw ex; + } + + /** + * @param msgId + */ + private void error(String msgId) + { + TransferProcessingException ex = new TransferProcessingException(msgId); + log.error(ex.getMessage(), ex); + throw ex; + } + + /** + * @param node + * @return + */ + private ChildAssociationRef getPrimaryParent(TransferManifestNormalNode node) + { + List parents = node.getParentAssocs(); + for (ChildAssociationRef parent : parents) + { + if (parent.isPrimary()) + return parent; + } + return null; + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.transfer.manifest.TransferManifestProcessor#processTransferManifiestHeader(org.alfresco.repo + * .transfer.manifest.TransferManifestHeader) + */ + public void processTransferManifiestHeader(TransferManifestHeader header) + { + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.transfer.manifest.TransferManifestProcessor#startTransferManifest() + */ + public void startTransferManifest() + { + } + + /** + * @param nodeService + * the nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param contentService + * the contentService to set + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * @param nodeResolver + * the nodeResolver to set + */ + public void setNodeResolver(CorrespondingNodeResolver nodeResolver) + { + this.nodeResolver = nodeResolver; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/RepoSecondaryManifestProcessorImpl.java b/source/java/org/alfresco/repo/transfer/RepoSecondaryManifestProcessorImpl.java new file mode 100644 index 0000000000..301f6875fa --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/RepoSecondaryManifestProcessorImpl.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.transfer.manifest.TransferManifestDeletedNode; +import org.alfresco.repo.transfer.manifest.TransferManifestHeader; +import org.alfresco.repo.transfer.manifest.TransferManifestNodeHelper; +import org.alfresco.repo.transfer.manifest.TransferManifestNormalNode; +import org.alfresco.repo.transfer.manifest.TransferManifestProcessor; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.RegexQNamePattern; + +/** + * @author brian + * + */ +public class RepoSecondaryManifestProcessorImpl implements TransferManifestProcessor +{ + private NodeService nodeService; + private CorrespondingNodeResolver nodeResolver; + private String transferId; + + /** + * @param transferId + */ + public RepoSecondaryManifestProcessorImpl(String transferId) + { + this.transferId = transferId; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.transfer.manifest.TransferManifestProcessor#endTransferManifest() + */ + public void endTransferManifest() + { + // TODO Auto-generated method stub + + } + + /** + * + */ + public void processTransferManifestNode(TransferManifestDeletedNode node) + { + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.transfer.manifest.TransferManifestProcessor#processTransferManifestNode(org.alfresco.repo.transfer + * .manifest.TransferManifestNode) + */ + public void processTransferManifestNode(TransferManifestNormalNode node) + { + NodeRef correspondingNodeRef = nodeResolver.resolveCorrespondingNode(node.getNodeRef(), + TransferManifestNodeHelper.getPrimaryParentAssoc(node), node.getParentPath()).resolvedChild; + + if (correspondingNodeRef == null) + { + correspondingNodeRef = node.getNodeRef(); + } + + + //Process parent assocs... + List requiredAssocs = node.getParentAssocs(); + List currentAssocs = nodeService.getParentAssocs(correspondingNodeRef); + processParentChildAssociations(requiredAssocs, currentAssocs, correspondingNodeRef, false); + + //Process child assocs... + requiredAssocs = node.getChildAssocs(); + currentAssocs = nodeService.getChildAssocs(correspondingNodeRef); + processParentChildAssociations(requiredAssocs, currentAssocs, correspondingNodeRef, true); + + + //Process "target" peer associations (associations *from* this node) + List requiredPeerAssocs = node.getTargetAssocs(); + List currentPeerAssocs = nodeService.getTargetAssocs(correspondingNodeRef, RegexQNamePattern.MATCH_ALL); + processPeerAssociations(requiredPeerAssocs, currentPeerAssocs, correspondingNodeRef, true); + + //Process "source" peer associations (associations *to* this node) + requiredPeerAssocs = node.getSourceAssocs(); + currentPeerAssocs = nodeService.getSourceAssocs(correspondingNodeRef, RegexQNamePattern.MATCH_ALL); + processPeerAssociations(requiredPeerAssocs, currentPeerAssocs, correspondingNodeRef, true); + + } + + + /** + * @param requiredAssocs + * @param currentAssocs + * @param correspondingNodeRef + * @param isSource + */ + private void processPeerAssociations(List requiredAssocs, + List currentAssocs, NodeRef nodeRef, boolean isSource) + { + if (requiredAssocs == null) { + requiredAssocs = new ArrayList(); + } + if (currentAssocs == null) { + currentAssocs = new ArrayList(); + } + + List assocsToAdd = new ArrayList(); + List assocsToRemove = new ArrayList(); + + Map currentAssocMap = new HashMap(); + + for (AssociationRef currentAssoc : currentAssocs) { + NodeRef otherNode = isSource ? currentAssoc.getTargetRef() : currentAssoc.getSourceRef(); + currentAssocMap.put(otherNode, currentAssoc); + } + + for (AssociationRef requiredAssoc : requiredAssocs) + { + NodeRef otherNode = isSource ? requiredAssoc.getTargetRef() : requiredAssoc.getSourceRef(); + AssociationRef existingAssociation = currentAssocMap.remove(otherNode); + if (existingAssociation != null) { + //We already have an association with the required node. + //Check whether it is correct + if (!existingAssociation.getTypeQName().equals(requiredAssoc.getTypeQName())) { + //No, the existing one doesn't match the required one + assocsToRemove.add(existingAssociation); + assocsToAdd.add(requiredAssoc); + } + } else { + //We don't have an existing association with this required node + //Check that the required node exists in this repo, and record it for adding + //if it does + if (nodeService.exists(otherNode)) { + assocsToAdd.add(requiredAssoc); + } + } + } + //Once we get here, any entries remaining in currentParentMap are associations that need to be deleted. + assocsToRemove.addAll(currentAssocMap.values()); + //Deal with associations to be removed + for (AssociationRef assocToRemove : assocsToRemove) { + nodeService.removeAssociation(assocToRemove.getSourceRef(), assocToRemove.getTargetRef(), assocToRemove.getTypeQName()); + } + //Deal with associations to be added + for (AssociationRef assocToAdd : assocsToAdd) { + NodeRef source = isSource ? nodeRef : assocToAdd.getSourceRef(); + NodeRef target = isSource ? assocToAdd.getTargetRef() : nodeRef; + nodeService.createAssociation(source, target, assocToAdd.getTypeQName()); + } + } + + + private void processParentChildAssociations(List requiredAssocs, + List currentAssocs, NodeRef nodeRef, boolean isParent) { + + if (requiredAssocs == null) { + requiredAssocs = new ArrayList(); + } + if (currentAssocs == null) { + currentAssocs = new ArrayList(); + } + + List assocsToAdd = new ArrayList(); + List assocsToRemove = new ArrayList(); + + Map currentAssocMap = new HashMap(); + + for (ChildAssociationRef currentAssoc : currentAssocs) { + if (!currentAssoc.isPrimary()) { + NodeRef key = isParent ? currentAssoc.getChildRef() : currentAssoc.getParentRef(); + currentAssocMap.put(key, currentAssoc); + } + } + + for (ChildAssociationRef requiredAssoc : requiredAssocs) + { + // We skip the primary parent, since this has already been handled + if (!requiredAssoc.isPrimary()) + { + NodeRef otherNode = isParent ? requiredAssoc.getChildRef() : requiredAssoc.getParentRef(); + ChildAssociationRef existingAssociation = currentAssocMap.remove(otherNode); + if (existingAssociation != null) { + //We already have an association with the required parent. + //Check whether it is correct + if (!existingAssociation.getQName().equals(requiredAssoc.getQName()) || + !existingAssociation.getTypeQName().equals(requiredAssoc.getTypeQName())) { + //No, the existing one doesn't match the required one + assocsToRemove.add(existingAssociation); + assocsToAdd.add(requiredAssoc); + } + } else { + //We don't have an existing association with this required parent + //Check that the requiredParent exists in this repo, and record it for adding + //if it does + if (nodeService.exists(otherNode)) { + assocsToAdd.add(requiredAssoc); + } + } + } + } + //Once we get here, any entries remaining in currentParentMap are associations that need to be deleted. + assocsToRemove.addAll(currentAssocMap.values()); + //Deal with associations to be removed + for (ChildAssociationRef assocToRemove : assocsToRemove) { + nodeService.removeChildAssociation(assocToRemove); + } + //Deal with associations to be added + for (ChildAssociationRef assocToAdd : assocsToAdd) { + NodeRef parent = isParent ? nodeRef : assocToAdd.getParentRef(); + NodeRef child = isParent ? assocToAdd.getChildRef() : nodeRef; + nodeService.addChild(parent, child, + assocToAdd.getTypeQName(), assocToAdd.getQName()); + } + } + + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.transfer.manifest.TransferManifestProcessor#processTransferManifiestHeader(org.alfresco.repo + * .transfer.manifest.TransferManifestHeader) + */ + public void processTransferManifiestHeader(TransferManifestHeader header) + { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.transfer.manifest.TransferManifestProcessor#startTransferManifest() + */ + public void startTransferManifest() + { + // TODO Auto-generated method stub + + } + + /** + * @param nodeService + * the nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param nodeResolver + * the nodeResolver to set + */ + public void setNodeResolver(CorrespondingNodeResolver nodeResolver) + { + this.nodeResolver = nodeResolver; + } + + + +} diff --git a/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java b/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java new file mode 100644 index 0000000000..5b9539c001 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImpl.java @@ -0,0 +1,633 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transfer.manifest.TransferManifestProcessor; +import org.alfresco.repo.transfer.manifest.XMLTransferManifestReader; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.cmr.transfer.TransferReceiver; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.FileCopyUtils; + +/** + * @author brian + * + */ +public class RepoTransferReceiverImpl implements TransferReceiver +{ + private final static Log log = LogFactory.getLog(RepoTransferReceiverImpl.class); + + private static final String MSG_FAILED_TO_CREATE_STAGING_FOLDER = "transfer_service.receiver.failed_to_create_staging_folder"; + private static final String MSG_TRANSFER_LOCK_FOLDER_NOT_FOUND = "transfer_service.receiver.lock_folder_not_found"; + private static final String MSG_TRANSFER_TEMP_FOLDER_NOT_FOUND = "transfer_service.receiver.temp_folder_not_found"; + private static final String MSG_TRANSFER_LOCK_UNAVAILABLE = "transfer_service.receiver.lock_unavailable"; + private static final String MSG_INBOUND_TRANSFER_FOLDER_NOT_FOUND = "transfer_service.receiver.record_folder_not_found"; + private static final String MSG_NOT_LOCK_OWNER = "transfer_service.receiver.not_lock_owner"; + private static final String MSG_ERROR_WHILE_ENDING_TRANSFER = "transfer_service.receiver.error_ending_transfer"; + private static final String MSG_ERROR_WHILE_STAGING_SNAPSHOT = "transfer_service.receiver.error_staging_snapshot"; + private static final String MSG_ERROR_WHILE_STAGING_CONTENT = "transfer_service.receiver.error_staging_content"; + private static final String MSG_NO_SNAPSHOT_RECEIVED = "transfer_service.receiver.no_snapshot_received"; + private static final String MSG_ERROR_WHILE_COMMITTING_TRANSFER = "transfer_service.receiver.error_committing_transfer"; + + private static final String LOCK_FILE_NAME = ".lock"; + private static final QName LOCK_QNAME = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, LOCK_FILE_NAME); + private static final String SNAPSHOT_FILE_NAME = "snapshot.xml"; + + private NodeService nodeService; + private SearchService searchService; + private TransactionService transactionService; + private String transferLockFolderPath; + private String inboundTransferRecordsPath; + private String rootStagingDirectory; + private String transferTempFolderPath; + private ManifestProcessorFactory manifestProcessorFactory; + private BehaviourFilter behaviourFilter; + + + private NodeRef transferLockFolder; + private NodeRef transferTempFolder; + private NodeRef inboundTransferRecordsFolder; + + public void init() + { + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "searchService", searchService); + PropertyCheck.mandatory(this, "transactionService", transactionService); + PropertyCheck.mandatory(this, "transferLockFolderPath", transferLockFolderPath); + PropertyCheck.mandatory(this, "inboundTransferRecordsPath", inboundTransferRecordsPath); + PropertyCheck.mandatory(this, "rootStagingDirectory", rootStagingDirectory); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.web.scripts.transfer.TransferReceiver#getStagingFolder(org.alfresco.service.cmr.repository. + * NodeRef) + */ + public File getStagingFolder(String transferId) + { + if (transferId == null) + { + throw new IllegalArgumentException("transferId = " + transferId); + } + NodeRef transferNodeRef = new NodeRef(transferId); + File tempFolder; + String tempFolderPath = rootStagingDirectory + "/" + transferNodeRef.getId(); + tempFolder = new File(tempFolderPath); + if (!tempFolder.exists()) + { + if (!tempFolder.mkdirs()) + { + tempFolder = null; + throw new TransferException(MSG_FAILED_TO_CREATE_STAGING_FOLDER, new Object[] {transferId}); + } + } + return tempFolder; + + } + + private NodeRef getLockFolder() + { + // Have we already resolved the node that is the parent of the lock node? + // If not then do so. + if (transferLockFolder == null) + { + synchronized (this) + { + if (transferLockFolder == null) + { + ResultSet rs = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, + SearchService.LANGUAGE_LUCENE, "PATH:\"" + transferLockFolderPath + "\""); + if (rs.length() > 0) + { + transferLockFolder = rs.getNodeRef(0); + } else + { + throw new TransferException(MSG_TRANSFER_LOCK_FOLDER_NOT_FOUND, new Object[] {transferLockFolderPath}); + } + } + } + } + return transferLockFolder; + + } + + public NodeRef getTempFolder(String transferId) + { + // Have we already resolved the node that is the temp folder? + // If not then do so. + if (transferTempFolder == null) + { + synchronized (this) + { + if (transferTempFolder == null) + { + ResultSet rs = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, + SearchService.LANGUAGE_LUCENE, "PATH:\"" + transferTempFolderPath + "\""); + if (rs.length() > 0) + { + transferTempFolder = rs.getNodeRef(0); + } else + { + throw new TransferException(MSG_TRANSFER_TEMP_FOLDER_NOT_FOUND, new Object[] {transferId, transferTempFolderPath}); + } + } + } + } + + NodeRef transferNodeRef = new NodeRef(transferId); + String tempTransferFolderName = transferNodeRef.getId(); + NodeRef tempFolderNode = null; + QName folderName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, tempTransferFolderName); + + // Do we already have a temp folder for this transfer? + List tempChildren = nodeService.getChildAssocs(transferTempFolder, + RegexQNamePattern.MATCH_ALL, folderName); + if (tempChildren.isEmpty()) + { + // No, we don't have a temp folder for this transfer yet. Create it... + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, tempTransferFolderName); + tempFolderNode = nodeService.createNode(transferTempFolder, ContentModel.ASSOC_CONTAINS, folderName, + ContentModel.TYPE_FOLDER, props).getChildRef(); + } else + { + // Yes, we do have a temp folder for this transfer already. Return it. + tempFolderNode = tempChildren.get(0).getChildRef(); + } + return tempFolderNode; + + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.web.scripts.transfer.TransferReceiver#start() + */ + public String start() + { + log.debug("start"); + final NodeRef relatedTransferRecord = createTransferRecord(); + final NodeRef lockFolder = getLockFolder(); + + RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper(); + try + { + txHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, LOCK_FILE_NAME); + props.put(TransferModel.PROP_TRANSFER_ID, relatedTransferRecord.toString()); + + log.error("Creating transfer lock associated with this transfer record: " + relatedTransferRecord); + ChildAssociationRef assoc = nodeService.createNode(lockFolder, ContentModel.ASSOC_CONTAINS, + LOCK_QNAME, TransferModel.TYPE_TRANSFER_LOCK, props); + log.error("Transfer lock created as node " + assoc.getChildRef()); + return assoc.getChildRef(); + } + }, false, true); + } + catch (DuplicateChildNodeNameException ex) + { + log.debug("lock is already taken"); + // lock is already taken. + throw new TransferException(MSG_TRANSFER_LOCK_UNAVAILABLE); + } + String transferId = relatedTransferRecord.toString(); + getStagingFolder(transferId); + return transferId; + } + + /** + * @return + */ + private NodeRef createTransferRecord() + { + log.debug("->createTransferRecord"); + if (inboundTransferRecordsFolder == null) + { + synchronized (this) + { + if (inboundTransferRecordsFolder == null) + { + log.debug("Trying to find transfer records folder: " + inboundTransferRecordsPath); + ResultSet rs = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, + SearchService.LANGUAGE_LUCENE, "PATH:\"" + inboundTransferRecordsPath + "\""); + if (rs.length() > 0) + { + inboundTransferRecordsFolder = rs.getNodeRef(0); + log.debug("Found inbound transfer records folder: " + inboundTransferRecordsFolder); + } else + { + throw new TransferException(MSG_INBOUND_TRANSFER_FOLDER_NOT_FOUND, new Object[] {inboundTransferRecordsPath}); + } + } + } + } + SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssSSSZ"); + String timeNow = format.format(new Date()); + QName recordName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, timeNow); + + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, timeNow); + + log.debug("Creating transfer record with name: " + timeNow); + ChildAssociationRef assoc = nodeService.createNode(inboundTransferRecordsFolder, ContentModel.ASSOC_CONTAINS, + recordName, ContentModel.TYPE_CONTENT, props); + log.debug("<-createTransferRecord: " + assoc.getChildRef()); + return assoc.getChildRef(); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.web.scripts.transfer.TransferReceiver#end(org.alfresco.service.cmr.repository.NodeRef) + */ + public void end(final String transferId) + { + log.debug("end transferId:" + transferId); + if (transferId == null) + { + throw new IllegalArgumentException("transferId = " + transferId); + } + + try + { + // We remove the lock node in a separate transaction, since it was created in a separate transaction + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + // Find the lock node + NodeRef lockId = getLockNode(); + if (lockId != null) + { + if (!testLockedTransfer(lockId, transferId)) + { + throw new TransferException(MSG_NOT_LOCK_OWNER, new Object[] {transferId}); + } + // Delete the lock node. + log.debug("delete lock node :" + lockId); + nodeService.deleteNode(lockId); + + } + return null; + } + }, false, true); + log.debug("delete staging folder " + transferId); + // Delete the staging folder. + File stagingFolder = getStagingFolder(transferId); + deleteFile(stagingFolder); + log.debug("Staging folder deleted"); + } + catch (TransferException ex) + { + throw ex; + } + catch (Exception ex) + { + throw new TransferException(MSG_ERROR_WHILE_ENDING_TRANSFER, ex); + } + } + + public void abort(String transferId) throws TransferException + { + //TODO Think about the relationship between abort and end. + end(transferId); + } + + public void prepare(String transferId) throws TransferException + { + } + + /** + * @param stagingFolder + */ + private void deleteFile(File file) + { + if (!file.isDirectory()) file.delete(); + File[] fileList = file.listFiles(); + if (fileList != null) { + for (File currentFile : fileList) { + deleteFile(currentFile); + } + } + file.delete(); + } + + private NodeRef getLockNode() + { + final NodeRef lockFolder = getLockFolder(); + List assocs = nodeService.getChildAssocs(lockFolder, ContentModel.ASSOC_CONTAINS, + LOCK_QNAME); + NodeRef lockId = assocs.size() == 0 ? null : assocs.get(0).getChildRef(); + return lockId; + } + + private boolean testLockedTransfer(NodeRef lockId, String transferId) + { + if (lockId == null) + { + throw new IllegalArgumentException("lockId = null"); + } + if (transferId == null) + { + throw new IllegalArgumentException("transferId = null"); + } + String currentTransferId = (String) nodeService.getProperty(lockId, TransferModel.PROP_TRANSFER_ID); + // Check that the lock is held for the specified transfer (error if not) + return (transferId.equals(currentTransferId)); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.service.cmr.transfer.TransferReceiver#nudgeLock(java.lang.String) + */ + public void nudgeLock(final String transferId) throws TransferException + { + if (transferId == null) + throw new IllegalArgumentException("transferId = null"); + + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + // Find the lock node + NodeRef lockId = getLockNode(); + // Check that the specified transfer is the one that owns the lock + if (!testLockedTransfer(lockId, transferId)) + { + throw new TransferException(MSG_NOT_LOCK_OWNER); + } + // Just write the lock file name again (no change, but forces the modified time to be updated) + nodeService.setProperty(lockId, ContentModel.PROP_NAME, LOCK_FILE_NAME); + return null; + } + }, false, true); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.service.cmr.transfer.TransferReceiver#saveSnapshot(java.io.InputStream) + */ + public void saveSnapshot(String transferId, InputStream openStream) throws TransferException + { + log.debug("save snapshot transferId=" + transferId); + // Check that this transfer owns the lock and give it a nudge to stop it expiring + nudgeLock(transferId); + File snapshotFile = new File(getStagingFolder(transferId), SNAPSHOT_FILE_NAME); + try + { + if (snapshotFile.createNewFile()) + { + FileCopyUtils.copy(openStream, new FileOutputStream(snapshotFile)); + } + log.debug("saved snapshot for transferId=" + transferId); + } + catch (Exception ex) + { + throw new TransferException(MSG_ERROR_WHILE_STAGING_SNAPSHOT, ex); + } + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.service.cmr.transfer.TransferReceiver#saveContent(java.lang.String, java.lang.String, + * java.io.InputStream) + */ + public void saveContent(String transferId, String contentFileId, InputStream contentStream) + throws TransferException + { + nudgeLock(transferId); + File stagedFile = new File(getStagingFolder(transferId), contentFileId); + try + { + if (stagedFile.createNewFile()) + { + FileCopyUtils.copy(contentStream, new BufferedOutputStream(new FileOutputStream(stagedFile))); + } + } + catch (Exception ex) + { + throw new TransferException(MSG_ERROR_WHILE_STAGING_CONTENT, ex); + } + } + + public void commit(String transferId) throws TransferException + { + log.debug("commit transferId=" + transferId); + try + { + nudgeLock(transferId); + List commitProcessors = manifestProcessorFactory.getCommitProcessors(this, transferId); + + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + SAXParser parser = saxParserFactory.newSAXParser(); + File snapshotFile = getSnapshotFile(transferId); + + if (snapshotFile.exists()) + { + log.debug("processing manifest file:" + snapshotFile.getAbsolutePath()); + //We parse the file as many times as we have processors + for (TransferManifestProcessor processor : commitProcessors) + { + XMLTransferManifestReader reader = new XMLTransferManifestReader(processor); + behaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE); + try + { + parser.parse(snapshotFile, reader); + } + finally + { + behaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE); + } + nudgeLock(transferId); + parser.reset(); + } + } + else + { + log.debug("no snapshot received"); + throw new TransferException(MSG_NO_SNAPSHOT_RECEIVED); + } + + + /** + * Successfully transfred + */ + log.debug("commit success transferId=" + transferId); + + } + catch (TransferException ex) + { + log.debug("unable to commit", ex); + throw ex; + } + catch (Exception ex) + { + log.debug("unable to commit", ex); + throw new TransferException(MSG_ERROR_WHILE_COMMITTING_TRANSFER, ex); + } + finally + { + /** + * Clean up at the end of the transfer + */ + try + { + log.debug("calling end"); + end(transferId); + log.debug("called end"); + } + catch (Exception ex) + { + log.error("Failed to clean up transfer. Lock may still be in place: " + transferId); + } + } + } + + private File getSnapshotFile(String transferId) + { + return new File(getStagingFolder(transferId), SNAPSHOT_FILE_NAME); + } + + /** + * @param searchService + * the searchService to set + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + /** + * @param transactionService + * the transactionService to set + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * @param transferLockFolderPath + * the transferLockFolderPath to set + */ + public void setTransferLockFolderPath(String transferLockFolderPath) + { + this.transferLockFolderPath = transferLockFolderPath; + } + + /** + * @param transferTempFolderPath the transferTempFolderPath to set + */ + public void setTransferTempFolderPath(String transferTempFolderPath) + { + this.transferTempFolderPath = transferTempFolderPath; + } + + /** + * @param rootStagingDirectory + * the rootTransferFolder to set + */ + public void setRootStagingDirectory(String rootStagingDirectory) + { + this.rootStagingDirectory = rootStagingDirectory; + } + + /** + * @param inboundTransferRecordsPath + * the inboundTransferRecordsPath to set + */ + public void setInboundTransferRecordsPath(String inboundTransferRecordsPath) + { + this.inboundTransferRecordsPath = inboundTransferRecordsPath; + } + + /** + * @param nodeService + * the nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param manifestProcessorFactory the manifestProcessorFactory to set + */ + public void setManifestProcessorFactory(ManifestProcessorFactory manifestProcessorFactory) + { + this.manifestProcessorFactory = manifestProcessorFactory; + } + + /** + * @param behaviourFilter the behaviourFilter to set + */ + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImplTest.java b/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImplTest.java new file mode 100644 index 0000000000..71ab228156 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/RepoTransferReceiverImplTest.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.Serializable; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.transfer.manifest.TransferManifestDeletedNode; +import org.alfresco.repo.transfer.manifest.TransferManifestHeader; +import org.alfresco.repo.transfer.manifest.TransferManifestNode; +import org.alfresco.repo.transfer.manifest.TransferManifestNormalNode; +import org.alfresco.repo.transfer.manifest.XMLTransferManifestWriter; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseAlfrescoSpringTest; +import org.alfresco.util.GUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tools.ant.filters.StringInputStream; + +/** + * Unit test for RepoTransferReceiverImpl + * + * @author Brian Remmington + */ +public class RepoTransferReceiverImplTest extends BaseAlfrescoSpringTest +{ + private static int fileCount = 0; + private static final Log log = LogFactory.getLog(RepoTransferReceiverImplTest.class); + + private RepoTransferReceiverImpl receiver; + private String dummyContent; + private byte[] dummyContentBytes; + + /** + * Called during the transaction setup + */ + @SuppressWarnings("deprecation") + protected void onSetUpInTransaction() throws Exception + { + System.out.println("java.io.tmpdir == " + System.getProperty("java.io.tmpdir")); + super.onSetUpInTransaction(); + + // Get the required services + this.receiver = (RepoTransferReceiverImpl) this.getApplicationContext().getBean("transferReceiver"); + this.dummyContent = "This is some dummy content."; + this.dummyContentBytes = dummyContent.getBytes("UTF-8"); + } + + public void testStartAndEnd() throws Exception + { + String transferId = receiver.start(); + System.out.println("TransferId == " + transferId); + + File stagingFolder = receiver.getStagingFolder(transferId); + assertTrue(receiver.getStagingFolder(transferId).exists()); + + try + { + receiver.start(); + fail("Successfully started twice!"); + } catch (TransferException ex) + { + // Expected + } + try + { + receiver.end(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate()).toString()); + fail("Successfully ended with transfer id that doesn't own lock."); + } catch (TransferException ex) + { + // Expected + } + receiver.end(transferId); + assertFalse(stagingFolder.exists()); + + receiver.end(receiver.start()); + } + + public void testSaveContent() throws Exception + { + String transferId = receiver.start(); + try + { + String contentId = "mytestcontent"; + receiver.saveContent(transferId, contentId, new ByteArrayInputStream(dummyContentBytes)); + File contentFile = new File(receiver.getStagingFolder(transferId), contentId); + assertTrue(contentFile.exists()); + assertEquals(dummyContentBytes.length, contentFile.length()); + } finally + { + receiver.end(transferId); + } + } + + public void testSaveSnapshot() throws Exception + { + String transferId = receiver.start(); + File snapshotFile = null; + try + { + TransferManifestNode node = createContentNode(transferId); + List nodes = new ArrayList(); + nodes.add(node); + String snapshot = createSnapshot(nodes); + + receiver.saveSnapshot(transferId, new StringInputStream(snapshot, "UTF-8")); + + File stagingFolder = receiver.getStagingFolder(transferId); + snapshotFile = new File(stagingFolder, "snapshot.xml"); + assertTrue(snapshotFile.exists()); + assertEquals(snapshot.getBytes("UTF-8").length, snapshotFile.length()); + } finally + { + receiver.end(transferId); + if (snapshotFile != null) { + assertFalse(snapshotFile.exists()); + } + } + } + + public void testBasicCommit() throws Exception { + String transferId = receiver.start(); + try + { + TransferManifestNode node = createContentNode(transferId); + List nodes = new ArrayList(); + nodes.add(node); + String snapshot = createSnapshot(nodes); + + receiver.saveSnapshot(transferId, new StringInputStream(snapshot, "UTF-8")); + receiver.saveContent(transferId, node.getUuid(), new ByteArrayInputStream(dummyContentBytes)); + receiver.commit(transferId); + + assertTrue(nodeService.exists(node.getNodeRef())); + nodeService.deleteNode(node.getNodeRef()); + } catch (Exception ex) + { + receiver.end(transferId); + throw ex; + } + + } + + public void testMoreComplexCommit() throws Exception { + String transferId = receiver.start(); + try + { + List nodes = new ArrayList(); + TransferManifestNormalNode node1 = createContentNode(transferId); + nodes.add(node1); + TransferManifestNormalNode node2 = createContentNode(transferId); + nodes.add(node2); + TransferManifestNode node3 = createContentNode(transferId); + nodes.add(node3); + TransferManifestNode node4 = createContentNode(transferId); + nodes.add(node4); + TransferManifestNode node5 = createContentNode(transferId); + nodes.add(node5); + TransferManifestNode node6 = createContentNode(transferId); + nodes.add(node6); + TransferManifestNode node7 = createContentNode(transferId); + nodes.add(node7); + TransferManifestNode node8 = createFolderNode(transferId); + nodes.add(node8); + TransferManifestNode node9 = createFolderNode(transferId); + nodes.add(node9); + TransferManifestNode node10 = createFolderNode(transferId); + nodes.add(node10); + TransferManifestNormalNode node11 = createFolderNode(transferId); + nodes.add(node11); + TransferManifestNode node12 = createFolderNode(transferId); + nodes.add(node12); + + associatePeers(node1, node2); + moveNode(node2, node11); + + String snapshot = createSnapshot(nodes); + + receiver.saveSnapshot(transferId, new StringInputStream(snapshot, "UTF-8")); + + for (TransferManifestNode node : nodes) { + receiver.saveContent(transferId, node.getUuid(), new ByteArrayInputStream(dummyContentBytes)); + } + receiver.commit(transferId); + + assertTrue(nodeService.getAspects(node1.getNodeRef()).contains(ContentModel.ASPECT_ATTACHABLE)); + assertFalse(nodeService.getSourceAssocs(node2.getNodeRef(), ContentModel.ASSOC_ATTACHMENTS).isEmpty()); + for (TransferManifestNode node : nodes) { + assertTrue(nodeService.exists(node.getNodeRef())); + } + } catch (Exception ex) + { + receiver.end(transferId); + throw ex; + } + + } + + public void testNodeDeleteAndRestore() throws Exception { + String transferId = receiver.start(); + try + { + List nodes = new ArrayList(); + TransferManifestNormalNode node1 = createContentNode(transferId); + nodes.add(node1); + TransferManifestNormalNode node2 = createContentNode(transferId); + nodes.add(node2); + TransferManifestNode node3 = createContentNode(transferId); + nodes.add(node3); + TransferManifestNode node4 = createContentNode(transferId); + nodes.add(node4); + TransferManifestNode node5 = createContentNode(transferId); + nodes.add(node5); + TransferManifestNode node6 = createContentNode(transferId); + nodes.add(node6); + TransferManifestNode node7 = createContentNode(transferId); + nodes.add(node7); + TransferManifestNode node8 = createFolderNode(transferId); + nodes.add(node8); + TransferManifestNode node9 = createFolderNode(transferId); + nodes.add(node9); + TransferManifestNode node10 = createFolderNode(transferId); + nodes.add(node10); + TransferManifestNormalNode node11 = createFolderNode(transferId); + nodes.add(node11); + TransferManifestNode node12 = createFolderNode(transferId); + nodes.add(node12); + + associatePeers(node1, node2); + moveNode(node2, node11); + + String snapshot = createSnapshot(nodes); + log.debug(snapshot); + receiver.saveSnapshot(transferId, new StringInputStream(snapshot, "UTF-8")); + + for (TransferManifestNode node : nodes) { + receiver.saveContent(transferId, node.getUuid(), new ByteArrayInputStream(dummyContentBytes)); + } + receiver.commit(transferId); + + assertTrue(nodeService.getAspects(node1.getNodeRef()).contains(ContentModel.ASPECT_ATTACHABLE)); + assertFalse(nodeService.getSourceAssocs(node2.getNodeRef(), ContentModel.ASSOC_ATTACHMENTS).isEmpty()); + for (TransferManifestNode node : nodes) { + assertTrue(nodeService.exists(node.getNodeRef())); + } + + //Now delete nodes 8, 2, and 11 (2 and 11 are parent/child) + TransferManifestDeletedNode deletedNode8 = createDeletedNode(node8); + TransferManifestDeletedNode deletedNode2 = createDeletedNode(node2); + TransferManifestDeletedNode deletedNode11 = createDeletedNode(node11); + transferId = receiver.start(); + snapshot = createSnapshot(Arrays.asList(new TransferManifestNode[] {deletedNode8, deletedNode2, deletedNode11})); + receiver.saveSnapshot(transferId, new StringInputStream(snapshot, "UTF-8")); + receiver.commit(transferId); + assertTrue(nodeService.exists(deletedNode8.getNodeRef())); + assertTrue(nodeService.hasAspect(deletedNode8.getNodeRef(), ContentModel.ASPECT_ARCHIVED)); + assertTrue(nodeService.exists(deletedNode2.getNodeRef())); + assertTrue(nodeService.hasAspect(deletedNode2.getNodeRef(), ContentModel.ASPECT_ARCHIVED)); + assertTrue(nodeService.exists(deletedNode11.getNodeRef())); + assertTrue(nodeService.hasAspect(deletedNode11.getNodeRef(), ContentModel.ASPECT_ARCHIVED)); + + //try to restore node 2. Expect an "orphan" failure, since its parent (node11) is deleted + transferId = receiver.start(); + snapshot = createSnapshot(Arrays.asList(new TransferManifestNode[] {node2})); + log.debug(snapshot); + receiver.saveSnapshot(transferId, new StringInputStream(snapshot, "UTF-8")); + receiver.saveContent(transferId, node2.getUuid(), new ByteArrayInputStream(dummyContentBytes)); + try + { + receiver.commit(transferId); + fail("Expected an exception!"); + } + catch (TransferException ex) + { + assertTrue(ex.getMsgId(), ex.getMsgId().contains("orphan")); + } + + } catch (Exception ex) + { + receiver.end(transferId); + throw ex; + } + + } + + /** + * @param nodeToDelete + * @return + */ + private TransferManifestDeletedNode createDeletedNode(TransferManifestNode nodeToDelete) + { + TransferManifestDeletedNode deletedNode = new TransferManifestDeletedNode(); + deletedNode.setNodeRef(new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, nodeToDelete.getNodeRef().getId())); + deletedNode.setParentPath(nodeToDelete.getParentPath()); + deletedNode.setPrimaryParentAssoc(nodeToDelete.getPrimaryParentAssoc()); + deletedNode.setUuid(nodeToDelete.getUuid()); + return deletedNode; + } + + /** + * @param childNode + * @param newParent + */ + private void moveNode(TransferManifestNormalNode childNode, TransferManifestNormalNode newParent) + { + List currentParents = childNode.getParentAssocs(); + List newParents = new ArrayList(); + + for (ChildAssociationRef parent : currentParents) { + if (!parent.isPrimary()) { + newParents.add(parent); + } else { + ChildAssociationRef newPrimaryAssoc = new ChildAssociationRef(ContentModel.ASSOC_CONTAINS, + newParent.getNodeRef(), parent.getQName(), parent.getChildRef(), true, -1); + newParents.add(newPrimaryAssoc); + childNode.setPrimaryParentAssoc(newPrimaryAssoc); + Path newParentPath = new Path(); + newParentPath.append(newParent.getParentPath()); + newParentPath.append(new Path.ChildAssocElement(newParent.getPrimaryParentAssoc())); + childNode.setParentPath(newParentPath); + } + } + childNode.setParentAssocs(newParents); + } + + private void associatePeers(TransferManifestNormalNode source, TransferManifestNormalNode target) { + List currentReferencedPeers = source.getTargetAssocs(); + if (currentReferencedPeers == null) { + currentReferencedPeers = new ArrayList(); + source.setTargetAssocs(currentReferencedPeers); + } + + List currentRefereePeers = target.getSourceAssocs(); + if (currentRefereePeers == null) { + currentRefereePeers = new ArrayList(); + target.setSourceAssocs(currentRefereePeers); + } + + Set aspects = source.getAspects(); + if (aspects == null ) { + aspects = new HashSet(); + source.setAspects(aspects); + } + aspects.add(ContentModel.ASPECT_ATTACHABLE); + + AssociationRef newAssoc = new AssociationRef(source.getNodeRef(), ContentModel.ASSOC_ATTACHMENTS, target.getNodeRef()); + currentRefereePeers.add(newAssoc); + currentReferencedPeers.add(newAssoc); + } + + private String createSnapshot(List nodes) throws Exception { + XMLTransferManifestWriter manifestWriter = new XMLTransferManifestWriter(); + StringWriter output = new StringWriter(); + manifestWriter.startTransferManifest(output); + TransferManifestHeader header = new TransferManifestHeader(); + header.setCreatedDate(new Date()); + manifestWriter.writeTransferManifestHeader(header); + for (TransferManifestNode node : nodes) { + manifestWriter.writeTransferManifestNode(node); + } + manifestWriter.endTransferManifest(); + + return output.toString(); + + } + + /** + * @return + */ + private TransferManifestNormalNode createContentNode(String transferId) throws Exception + { + TransferManifestNormalNode node = new TransferManifestNormalNode(); + String uuid = GUID.generate(); + NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, uuid); + node.setNodeRef(nodeRef); + node.setUuid(uuid); + byte[] dummyContent = "This is some dummy content.".getBytes("UTF-8"); + + node.setType(ContentModel.TYPE_CONTENT); + + NodeRef parentFolder = receiver.getTempFolder(transferId); + String nodeName = transferId + ".testnode" + getNameSuffix(); + + List parents = new ArrayList(); + ChildAssociationRef primaryAssoc = new ChildAssociationRef(ContentModel.ASSOC_CONTAINS, parentFolder, QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, nodeName), node.getNodeRef(), true, -1); + parents.add(primaryAssoc); + node.setParentAssocs(parents); + node.setParentPath(nodeService.getPath(parentFolder)); + node.setPrimaryParentAssoc(primaryAssoc); + + Map props = new HashMap(); + props.put(ContentModel.PROP_NODE_UUID, uuid); + props.put(ContentModel.PROP_NAME, nodeName); + ContentData contentData = new ContentData("/" + uuid, "text/plain", dummyContent.length, "UTF-8"); + props.put(ContentModel.PROP_CONTENT, contentData); + node.setProperties(props); + + return node; + } + + private TransferManifestNormalNode createFolderNode(String transferId) throws Exception + { + TransferManifestNormalNode node = new TransferManifestNormalNode(); + String uuid = GUID.generate(); + NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, uuid); + node.setNodeRef(nodeRef); + node.setUuid(uuid); + + node.setType(ContentModel.TYPE_FOLDER); + + NodeRef parentFolder = receiver.getTempFolder(transferId); + String nodeName = transferId + ".folder" + getNameSuffix(); + + List parents = new ArrayList(); + ChildAssociationRef primaryAssoc = new ChildAssociationRef(ContentModel.ASSOC_CONTAINS, parentFolder, QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, nodeName), node.getNodeRef(), true, -1); + parents.add(primaryAssoc); + node.setParentAssocs(parents); + node.setParentPath(nodeService.getPath(parentFolder)); + node.setPrimaryParentAssoc(primaryAssoc); + + Map props = new HashMap(); + props.put(ContentModel.PROP_NODE_UUID, uuid); + props.put(ContentModel.PROP_NAME, nodeName); + node.setProperties(props); + + return node; + } + + private String getNameSuffix() { + return "" + fileCount++; + } +} diff --git a/source/java/org/alfresco/repo/transfer/StandardNodeCrawlerImpl.java b/source/java/org/alfresco/repo/transfer/StandardNodeCrawlerImpl.java new file mode 100644 index 0000000000..e5b2846dbd --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/StandardNodeCrawlerImpl.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.transfer.NodeFilter; +import org.alfresco.service.cmr.transfer.NodeFinder; +import org.alfresco.service.cmr.transfer.TransferService; + +/** + * This class can be used to build a set of node references from a given starting point. The caller can provide a list + * of {@link NodeFinder} objects and a list of {@link NodeFilter} objects. Starting with the nodes supplied by the + * caller, the crawler uses the NodeFinder objects to find other nodes. Each node that is found is then passed to the + * NodeFilter objects to determine whether it should be included or ignored. Any included nodes are then fed back into + * the NodeFinder objects to continue the crawl. This class was originally written to assist users of the + * {@link TransferService} in combination with the {@link ChildAssociatedNodeFinder} and the {@link ContentClassFilter}. + * + * @author brian + * + */ +public class StandardNodeCrawlerImpl +{ + private ServiceRegistry serviceRegistry; + private List nodeFinders = new ArrayList(); + private List nodeFilters = new ArrayList(); + + /** + * + */ + public StandardNodeCrawlerImpl() + { + super(); + } + + /** + * @param serviceRegistry + */ + public StandardNodeCrawlerImpl(ServiceRegistry serviceRegistry) + { + super(); + this.serviceRegistry = serviceRegistry; + } + + /** + * @param nodeService + * the nodeService to set + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + public Set crawl(NodeRef... nodes) + { + return crawl(new HashSet(Arrays.asList(nodes))); + } + + public synchronized Set crawl(Set startingNodes) + { + init(); + Queue nodesToProcess = new LinkedList(); + nodesToProcess.addAll(startingNodes); + Set resultingNodeSet = new HashSet(89); + Set processedNodes = new HashSet(89); + + // Do we have any more nodes to process? + while (nodesToProcess.peek() != null) + { + // Yes, we do. Read the next noderef from the queue. + NodeRef thisNode = nodesToProcess.poll(); + // Check that we haven't already processed it. Skip it if we have, process it if we haven't + if (!processedNodes.contains(thisNode)) + { + // Record the fact that we're processing this node + processedNodes.add(thisNode); + // We check this node against any filters that are in place (the nodes + // that we were given to start with are always processed) + if (startingNodes.contains(thisNode) || includeNode(thisNode)) + { + resultingNodeSet.add(thisNode); + Set subsequentNodes = findSubsequentNodes(thisNode); + for (NodeRef node : subsequentNodes) + { + nodesToProcess.add(node); + } + } + } + } + return resultingNodeSet; + } + + /** + * + */ + private void init() + { + for (NodeFinder nodeFinder : this.nodeFinders) + { + nodeFinder.setServiceRegistry(serviceRegistry); + nodeFinder.init(); + } + for (NodeFilter nodeFilter : this.nodeFilters) + { + nodeFilter.setServiceRegistry(serviceRegistry); + nodeFilter.init(); + } + } + + /** + * @param thisNode + * @return + */ + private Set findSubsequentNodes(NodeRef thisNode) + { + Set foundNodes = new HashSet(89); + for (NodeFinder finder : nodeFinders) + { + foundNodes.addAll(finder.findFrom(thisNode)); + } + return foundNodes; + } + + /** + * @param thisNode + * @return + */ + private boolean includeNode(NodeRef thisNode) + { + boolean include = true; + for (int i = 0; include && (i < nodeFilters.size()); ++i) + { + include &= nodeFilters.get(i).accept(thisNode); + } + return include; + } + + public synchronized void setNodeFinders(NodeFinder... finders) + { + nodeFinders = Arrays.asList(finders); + } + + public synchronized void setNodeFilters(NodeFilter... filters) + { + nodeFilters = Arrays.asList(filters); + } +} diff --git a/source/java/org/alfresco/repo/transfer/TestTransferCallback.java b/source/java/org/alfresco/repo/transfer/TestTransferCallback.java new file mode 100644 index 0000000000..457cacb30a --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TestTransferCallback.java @@ -0,0 +1,36 @@ +package org.alfresco.repo.transfer; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.alfresco.service.cmr.transfer.TransferCallback; +import org.alfresco.service.cmr.transfer.TransferEvent; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class TestTransferCallback implements TransferCallback +{ + /** + * + */ + private static final long serialVersionUID = 1L; + private static Log logger = LogFactory.getLog(TestTransferCallback.class); + + public void processEvent(TransferEvent event) + { + logger.debug(event.toString()); + events.add(event); + } + + Queue events = new ConcurrentLinkedQueue(); + + /** + * Get the thread safe queue of events + * @return + */ + public Queue getEvents() + { + return events; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/Transfer.java b/source/java/org/alfresco/repo/transfer/Transfer.java new file mode 100644 index 0000000000..2daf1f0a02 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/Transfer.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import org.alfresco.service.cmr.transfer.TransferTarget; + +/** + * Information about a transfer which is in progress. + * + * @author Mark Rogers + */ +public class Transfer +{ + private String transferId; + private TransferTarget transferTarget; + + public void setTransferId(String transferId) + { + this.transferId = transferId; + } + + public String getTransferId() + { + return transferId; + } + + // may also have capabilities of the remote system here (for when we are + // transfering accross versions) + + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + else if (this == obj) + { + return true; + } + else if (obj instanceof Transfer == false) + { + return false; + } + Transfer that = (Transfer) obj; + return (this.transferId.equals(that.getTransferId())); + } + + public int hashCode() + { + return transferId.hashCode(); + } + + /** + * @param target + */ + public void setTransferTarget(TransferTarget target) + { + this.transferTarget = target; + } + + /** + * @return the transferTarget + */ + public TransferTarget getTransferTarget() + { + return transferTarget; + } + + public String toString() + { + return "TransferId" + transferId + ", target:" + transferTarget ; + } +} diff --git a/source/java/org/alfresco/repo/transfer/TransferActionExecuter.java b/source/java/org/alfresco/repo/transfer/TransferActionExecuter.java new file mode 100644 index 0000000000..739abcab34 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferActionExecuter.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.transfer.TransferDefinition; +import org.alfresco.service.cmr.transfer.TransferService; + +/** + * @author brian + * + */ +public class TransferActionExecuter extends ActionExecuterAbstractBase +{ + public static final String NAME = "transfer-node"; + public static final String PARAM_TRANSFER_TARGET = "target-name"; + private TransferService transferService; + + /** + * @param transferService the transferService to set + */ + public void setTransferService(TransferService transferService) + { + this.transferService = transferService; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + */ + @Override + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + TransferDefinition td = new TransferDefinition(); + Set nodes = new HashSet(); + nodes.add(actionedUponNodeRef); + td.setNodes(nodes); + transferService.transfer("transferMe", td); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) + */ + @Override + protected void addParameterDefinitions(List paramList) + { + //paramList.add(new ParameterDefinitionImpl(PARAM_TRANSFER_TARGET, DataTypeDefinition.TEXT, true, "Transfer Target Name")); + } + +} diff --git a/source/java/org/alfresco/repo/transfer/TransferAsyncAction.java b/source/java/org/alfresco/repo/transfer/TransferAsyncAction.java new file mode 100644 index 0000000000..6371ae3fe0 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferAsyncAction.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing + */ + +package org.alfresco.repo.transfer; + +import java.util.List; +import java.util.Set; + +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.transfer.TransferCallback; +import org.alfresco.service.cmr.transfer.TransferDefinition; +import org.alfresco.service.cmr.transfer.TransferService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Deploys a website to a remote server. + * + * TODO refactor and add to WCM services (when we support WCM deployment config) + * + * @author gavinc + */ +public class TransferAsyncAction extends ActionExecuterAbstractBase +{ + public static final String ASYNC_QUEUE_NAME = "deployment"; + + private TransferService transferService; + + private static Log logger = LogFactory.getLog(TransferAsyncAction.class); + + public void init() + { + super.name = "transfer-async"; + } + + + @Override + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + System.out.println("In TransferAsyncAction"); + + String targetName = (String)action.getParameterValue("targetName"); + TransferDefinition definition = (TransferDefinition)action.getParameterValue("definition"); + Set callback = (Set) action.getParameterValue("callbacks"); + + transferService.transfer(targetName, definition, callback); + } + + @Override + protected void addParameterDefinitions(List paramList) + { + } + +public void setTransferService(TransferService transferService) +{ + this.transferService = transferService; +} + +public TransferService getTransferService() +{ + return transferService; +} +} diff --git a/source/java/org/alfresco/repo/transfer/TransferCommons.java b/source/java/org/alfresco/repo/transfer/TransferCommons.java new file mode 100644 index 0000000000..0ea8bec66d --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferCommons.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +/** + * A bucket for little odds and ends for the transfer service. + * + * If this becomes a big class then refactor it away. + * + * @author Mark Rogers + */ +public class TransferCommons +{ + /** + * The Mime Part Name of the manifest file + */ + public final static String PART_NAME_MANIFEST = "manifest"; + + /** + * Mapping between contentUrl and part name. + * + * @param URL + * @return the part name + */ + public final static String URLToPartName(String contentUrl) + { + return contentUrl.substring(contentUrl.lastIndexOf('/')+1); + } +} diff --git a/source/java/org/alfresco/repo/transfer/TransferEventImpl.java b/source/java/org/alfresco/repo/transfer/TransferEventImpl.java new file mode 100644 index 0000000000..5c01abbce8 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferEventImpl.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import java.util.Date; + +import org.alfresco.service.cmr.transfer.TransferEvent; + +/** + * An abstract implementation of TransferEvent. + * Also implements RangedTransferEvent. + * @see TransferEvent + * @see RangedTransferEvent + */ +public abstract class TransferEventImpl implements TransferEvent +{ + private String message; + private TransferState state; + private boolean last = false; + private long range = 0; + private long position = 0; + private Date time = new Date(); + + public String getMessage() + { + return message; + } + + public Date getTime() + { + return time; + } + + public void setMessage(String message) + { + this.message = message; + } + + public void setRange(long range) + { + this.range = range; + } + + public void setPosition(long position) + { + this.position = position; + } + + public void setTransferState(TransferState state) + { + this.state = state; + } + + public void setTime(Date time) + { + this.time = time; + } + + public TransferState getTransferState() + { + return state; + } + + public void setLast(boolean last) + { + this.last = last; + } + + public boolean isLast() + { + return last; + } + + /** + * The position in the range + * @return + */ + public long getPosition() + { + return position; + } + + /** + * The maximum range + * @return + */ + public long getRange() + { + return range; + } + + public String toString() + { + return "TransferEventImpl : " + this.getTime() + ", " + this.getTransferState(); + } + + public boolean equals(Object obj) + { + if(obj instanceof TransferEventImpl) + { + TransferEventImpl other = (TransferEventImpl)obj; + if(other.getTransferState().equals(this.getTransferState()) && + other.getPosition() == this.getPosition() && + other.getTime().equals(this.getTime())) + { + return true; + } + } + return false; + } + + public int hashCode() + { + // discard any high bits + return (int)this.getTime().getTime(); + } + +} diff --git a/source/java/org/alfresco/repo/transfer/TransferEventProcessor.java b/source/java/org/alfresco/repo/transfer/TransferEventProcessor.java new file mode 100644 index 0000000000..aa1ab2ee41 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferEventProcessor.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; + +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.transfer.TransferCallback; +import org.alfresco.service.cmr.transfer.TransferEvent; +import org.alfresco.service.cmr.transfer.TransferEventCommittingStatus; +import org.alfresco.service.cmr.transfer.TransferEventEndState; +import org.alfresco.service.cmr.transfer.TransferEventEnterState; +import org.alfresco.service.cmr.transfer.TransferEventError; +import org.alfresco.service.cmr.transfer.TransferEventSendingContent; +import org.alfresco.service.cmr.transfer.TransferEventSendingManifest; +import org.alfresco.service.cmr.transfer.TransferEventSuccess; + +/** + * Class to bring together all the transfer event stuff. + * + * One processor instance for each transfer. + * + * Observer + * + * @author Mark Rogers + */ + + +public class TransferEventProcessor +{ + public Set observers = new HashSet(); + + LinkedBlockingQueue queue = new LinkedBlockingQueue(); + + + + public void addObserver(TransferCallback observer) + { + observers.add(observer); + } + + public void deleteObserver(TransferCallback observer) + { + observers.remove(observer); + } + + /** + * + */ + public TransferEventProcessor() + { + + } + + public void start() + { + setState(TransferEvent.TransferState.START); + notifyObservers(); + } + + public void success() + { + setState(TransferEvent.TransferState.SUCCESS); + + /** + * Write the success event + */ + TransferEventSuccess event = new TransferEventSuccess(); + event.setTransferState(TransferEvent.TransferState.SUCCESS); + event.setLast(true); + queue.add(event); + notifyObservers(); + } + + public void error(Exception exception) + { + setState(TransferEvent.TransferState.ERROR); + + /** + * Write the error event + */ + TransferEventError event = new TransferEventError(); + event.setTransferState(TransferEvent.TransferState.ERROR); + event.setLast(true); + event.setException(exception); + queue.add(event); + notifyObservers(); + } + + /** + * + * @param data + * @param range + * @param position + */ + public void sendContent(ContentData data, long range, long position) + { + setState(TransferEvent.TransferState.SENDING_CONTENT); + + TransferEventSendingContent event = new TransferEventSendingContent(); + event.setTransferState(TransferEvent.TransferState.SENDING_CONTENT); + event.setRange(range); + event.setPosition(position); + event.setSize(data.getSize()); + event.setMessage("sending content " + position + " of " + range + ", size: " + event.getSize()); + queue.add(event); + notifyObservers(); + } + + /** + * + * @param data + * @param range + * @param position + */ + public void sendManifest(long range, long position) + { + setState(TransferEvent.TransferState.SENDING_MANIFEST); + + TransferEventSendingManifest event = new TransferEventSendingManifest(); + event.setTransferState(TransferEvent.TransferState.SENDING_MANIFEST); + event.setRange(range); + event.setPosition(position); + event.setMessage("sending manifest"); + queue.add(event); + notifyObservers(); + } + + public void prepare() + { + setState(TransferEvent.TransferState.PREPARING); + notifyObservers(); + } + + /** + * + * @param range + * @param position + */ + public void committing(long range, long position) + { + setState(TransferEvent.TransferState.COMMITTING); + + TransferEventCommittingStatus event = new TransferEventCommittingStatus(); + event.setTransferState(TransferEvent.TransferState.COMMITTING); + event.setRange(range); + event.setPosition(position); + event.setMessage("committing " + position + " of " + range); + queue.add(event); + notifyObservers(); + + } + + public void abort() + { + + } + + private TransferEvent.TransferState currentState; + + private void setState(TransferEvent.TransferState state) + { + if(currentState != state) + { + if(currentState != null) + { + TransferEventImpl event = new TransferEventEndState(); + event.setMessage("End State: " + currentState); + event.setTransferState(state); + queue.add(event); + } + { + TransferEventImpl event = new TransferEventEnterState(); + event.setMessage("Enter State: " + state); + event.setTransferState(state); + queue.add(event); + } + currentState = state; + } + } + + + private void notifyObservers() + { + TransferEvent event = (TransferEvent)queue.poll(); + while(event != null) + { + // call the observers + for(TransferCallback callback : observers) + { + callback.processEvent(event); + } + event = (TransferEvent)queue.poll(); + } + } +} diff --git a/source/java/org/alfresco/repo/transfer/TransferMessage.java b/source/java/org/alfresco/repo/transfer/TransferMessage.java new file mode 100644 index 0000000000..431d4092a8 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferMessage.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +public interface TransferMessage +{ + String getMessage(); + +} diff --git a/source/java/org/alfresco/repo/transfer/TransferModel.java b/source/java/org/alfresco/repo/transfer/TransferModel.java new file mode 100644 index 0000000000..b3b744cab3 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferModel.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import org.alfresco.service.namespace.QName; + +/** + * Transfer Model Constants + * + * @author Mark Rogers + */ +public interface TransferModel +{ + static final String TRANSFER_MODEL_1_0_URI = "http://www.alfresco.org/model/transfer/1.0"; + + static final QName ASPECT_ENABLEABLE = QName.createQName(TRANSFER_MODEL_1_0_URI, "enableable"); +// static final QName ASSOC_IMAP_ATTACHMENTS_FOLDER = QName.createQName(IMAP_MODEL_1_0_URI, "attachmentsFolder"); + + /* + * Type : Transfer Group + */ + static final QName TYPE_TRANSFER_GROUP = QName.createQName(TRANSFER_MODEL_1_0_URI, "transferGroup"); + + /* + * Type : Transfer Target + */ + static final QName TYPE_TRANSFER_TARGET = QName.createQName(TRANSFER_MODEL_1_0_URI, "transferTarget"); + static final QName PROP_ENDPOINT_PROTOCOL = QName.createQName(TRANSFER_MODEL_1_0_URI, "endpointprotocol"); + static final QName PROP_ENDPOINT_HOST = QName.createQName(TRANSFER_MODEL_1_0_URI, "endpointhost"); + static final QName PROP_ENDPOINT_PORT = QName.createQName(TRANSFER_MODEL_1_0_URI, "endpointport"); + static final QName PROP_ENDPOINT_PATH = QName.createQName(TRANSFER_MODEL_1_0_URI, "endpointpath"); + static final QName PROP_USERNAME = QName.createQName(TRANSFER_MODEL_1_0_URI, "username"); + static final QName PROP_PASSWORD = QName.createQName(TRANSFER_MODEL_1_0_URI, "password"); + + static final QName PROP_ENABLED = QName.createQName(TRANSFER_MODEL_1_0_URI, "enabled"); + + /* + * Type : Transfer Lock + */ + static final QName TYPE_TRANSFER_LOCK = QName.createQName(TRANSFER_MODEL_1_0_URI, "transferLock"); + static final QName PROP_TRANSFER_ID = QName.createQName(TRANSFER_MODEL_1_0_URI, "transferId"); + + /* + * Type : Transfer report + */ + static final QName TYPE_TRANSFER_REPORT = QName.createQName(TRANSFER_MODEL_1_0_URI, "transferReport"); + +} diff --git a/source/java/org/alfresco/repo/transfer/TransferProcessingException.java b/source/java/org/alfresco/repo/transfer/TransferProcessingException.java new file mode 100644 index 0000000000..baff110ad5 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferProcessingException.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import org.alfresco.service.cmr.transfer.TransferException; + +/** + * @author brian + * + */ +public class TransferProcessingException extends TransferException +{ + private boolean fatal = false; + + /** + * @param msgId + * @param msgParams + * @param cause + */ + public TransferProcessingException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } + + /** + * @param msgId + * @param msgParams + */ + public TransferProcessingException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + /** + * @param msgId + * @param cause + */ + public TransferProcessingException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + /** + * @param msgId + */ + public TransferProcessingException(String msgId) + { + super(msgId); + } + + /** + * @return the fatal + */ + public boolean isFatal() + { + return fatal; + } + + /** + * @param fatal the fatal to set + */ + public void setFatal(boolean fatal) + { + this.fatal = fatal; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/TransferServiceImpl.java b/source/java/org/alfresco/repo/transfer/TransferServiceImpl.java new file mode 100644 index 0000000000..c40c97330a --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferServiceImpl.java @@ -0,0 +1,889 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ThreadPoolExecutor; +import org.alfresco.service.cmr.transfer.TransferEvent; + +import javax.transaction.HeuristicMixedException; +import javax.transaction.HeuristicRollbackException; +import javax.transaction.NotSupportedException; +import javax.transaction.RollbackException; +import javax.transaction.SystemException; +import javax.transaction.UserTransaction; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.jlan.smb.dcerpc.UUID; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transfer.manifest.TestTransferManifestProcessor; +import org.alfresco.repo.transfer.manifest.TransferManifestDeletedNode; +import org.alfresco.repo.transfer.manifest.TransferManifestHeader; +import org.alfresco.repo.transfer.manifest.TransferManifestNode; +import org.alfresco.repo.transfer.manifest.TransferManifestNodeFactory; +import org.alfresco.repo.transfer.manifest.TransferManifestNodeFactoryImpl; +import org.alfresco.repo.transfer.manifest.TransferManifestNodeHelper; +import org.alfresco.repo.transfer.manifest.TransferManifestNormalNode; +import org.alfresco.repo.transfer.manifest.TransferManifestProcessor; +import org.alfresco.repo.transfer.manifest.TransferManifestWriter; +import org.alfresco.repo.transfer.manifest.XMLTransferManifestReader; +import org.alfresco.repo.transfer.manifest.XMLTransferManifestWriter; +import org.alfresco.repo.transfer.report.TransferReporter; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.transfer.TransferCallback; +import org.alfresco.service.cmr.transfer.TransferDefinition; +import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.cmr.transfer.TransferService; +import org.alfresco.service.cmr.transfer.TransferTarget; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.xml.sax.SAXException; + + +public class TransferServiceImpl implements TransferService +{ + private static final String MSG_NO_HOME = "transfer_service.unable_to_find_transfer_home"; + private static final String MSG_NO_GROUP = "transfer_service.unable_to_find_transfer_group"; + private static final String MSG_NO_TARGET = "transfer_service.unable_to_find_transfer_target"; + private static final String MSG_TARGET_EXISTS = "transfer_service.target_exists"; + private static final String MSG_NO_NODES = "transfer_service.no_nodes"; + + private static Log logger = LogFactory.getLog(TransferServiceImpl.class); + + public void init() + { + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "searchService", getSearchService()); + PropertyCheck.mandatory(this, "transferSpaceQuery", transferSpaceQuery); + PropertyCheck.mandatory(this, "defaultTransferGroup", defaultTransferGroup); + PropertyCheck.mandatory(this, "transmitter", transmitter); + PropertyCheck.mandatory(this, "namespaceResolver", transmitter); + PropertyCheck.mandatory(this, "actionService", actionService); + PropertyCheck.mandatory(this, "transactionService", transactionService); + } + + private String transferSpaceQuery; + private String defaultTransferGroup; + private NodeService nodeService; + private SearchService searchService; + private TransferTransmitter transmitter; + private TransactionService transactionService; + private ActionService actionService; + private TransferManifestNodeFactory transferManifestNodeFactory; + private TransferReporter transferReporter; + + /** + * create transfer target + */ + public TransferTarget createTransferTarget(String name, String title, String description, String endpointProtocol, String endpointHost, int endpointPort, String endpointPath, String username, char[] password) + { + /** + * Check whether name is already used + */ + NodeRef dummy = lookupTransferTarget(name); + if(dummy != null) + { + throw new TransferException(MSG_TARGET_EXISTS, new Object[]{name} ); + } + + Map properties = new HashMap(); + + // type properties + properties.put(TransferModel.PROP_ENDPOINT_HOST, endpointHost); + properties.put(TransferModel.PROP_ENDPOINT_PORT, endpointPort); + properties.put(TransferModel.PROP_ENDPOINT_PROTOCOL, endpointProtocol); + properties.put(TransferModel.PROP_ENDPOINT_PATH, endpointPath); + properties.put(TransferModel.PROP_USERNAME, username); + properties.put(TransferModel.PROP_PASSWORD, encrypt(password)); + + // titled aspect + properties.put(ContentModel.PROP_TITLE, title); + properties.put(ContentModel.PROP_NAME, name); + properties.put(ContentModel.PROP_DESCRIPTION, description); + + // enableable aspect + properties.put(TransferModel.PROP_ENABLED, Boolean.TRUE); + + NodeRef home = getTransferHome(); + + /** + * Work out which group the transfer target is for, in this case the default group. + */ + NodeRef defaultGroup = nodeService.getChildByName(home, ContentModel.ASSOC_CONTAINS, defaultTransferGroup); + + /** + * Go ahead and create the new node + */ + ChildAssociationRef ref = nodeService.createNode(defaultGroup, ContentModel.ASSOC_CONTAINS, QName.createQName(TransferModel.TRANSFER_MODEL_1_0_URI, name), TransferModel.TYPE_TRANSFER_TARGET, properties); + + /** + * Now create a new TransferTarget object to return to the caller. + */ + TransferTargetImpl newTarget = new TransferTargetImpl(); + mapTransferTarget(ref.getChildRef(), newTarget); + + return newTarget; + } + + /** + * Get all transfer targets + */ + public Set getTransferTargets() + { + NodeRef home = getTransferHome(); + + Set ret = new HashSet(); + + // get all groups + List groups = nodeService.getChildAssocs(home); + + // for each group + for(ChildAssociationRef group : groups) + { + NodeRef groupNode = group.getChildRef(); + Listchildren = nodeService.getChildAssocs(groupNode, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); + + for(ChildAssociationRef child : children) + { + if(nodeService.getType(child.getChildRef()).equals(TransferModel.TYPE_TRANSFER_TARGET)) + { + TransferTargetImpl newTarget = new TransferTargetImpl(); + mapTransferTarget(child.getChildRef(), newTarget); + ret.add(newTarget); + } + } + } + + return ret; + } + + /** + * Get all transfer targets in the specified group + */ + public Set getTransferTargets(String groupName) + { + NodeRef home = getTransferHome(); + + Set ret = new HashSet(); + + // get group with assoc groupName + NodeRef groupNode = nodeService.getChildByName(home, ContentModel.ASSOC_CONTAINS, groupName); + + if(groupNode == null) + { + // No transfer group. + throw new TransferException(MSG_NO_GROUP, new Object[]{groupName}); + } + + /** + * Get children of groupNode + */ + Listchildren = nodeService.getChildAssocs(groupNode, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL); + + for(ChildAssociationRef child : children) + { + if(nodeService.getType(child.getChildRef()).equals(TransferModel.TYPE_TRANSFER_TARGET)) + { + TransferTargetImpl newTarget = new TransferTargetImpl(); + mapTransferTarget(child.getChildRef(), newTarget); + ret.add(newTarget); + } + } + return ret; + } + + /** + * + */ + public void deleteTransferTarget(String name) + { + NodeRef nodeRef = lookupTransferTarget(name); + + if(nodeRef == null) + { + // target does not exist + throw new TransferException(MSG_NO_TARGET, new Object[]{name} ); + } + nodeService.deleteNode(nodeRef); + } + + /** + * Enables/Disables the named transfer target + */ + public void enableTransferTarget(String name, boolean enable) + { + NodeRef nodeRef = lookupTransferTarget(name); + nodeService.setProperty(nodeRef, TransferModel.PROP_ENABLED, new Boolean(enable)); + } + + /** + * + */ + public TransferTarget getTransferTarget(String name) + { + NodeRef nodeRef = lookupTransferTarget(name); + + if(nodeRef == null) + { + // target does not exist + throw new TransferException(MSG_NO_TARGET, new Object[]{name} ); + } + TransferTargetImpl newTarget = new TransferTargetImpl(); + mapTransferTarget(nodeRef, newTarget); + + return newTarget; + } + + /** + * + */ + public TransferTarget updateTransferTarget(TransferTarget update) + { + NodeRef nodeRef = lookupTransferTarget(update.getName()); + if(nodeRef == null) + { + // target does not exist + throw new TransferException(MSG_NO_TARGET, new Object[]{update.getName()} ); + } + + Map properties = new HashMap(); + properties.put(TransferModel.PROP_ENDPOINT_HOST, update.getEndpointHost()); + properties.put(TransferModel.PROP_ENDPOINT_PORT, update.getEndpointPort()); + properties.put(TransferModel.PROP_ENDPOINT_PROTOCOL, update.getEndpointProtocol()); + properties.put(TransferModel.PROP_ENDPOINT_PATH, update.getEndpointPath()); + properties.put(TransferModel.PROP_USERNAME, update.getUsername()); + properties.put(TransferModel.PROP_PASSWORD, encrypt(update.getPassword())); + + // titled aspect + properties.put(ContentModel.PROP_TITLE, update.getTitle()); + properties.put(ContentModel.PROP_NAME, update.getName()); + properties.put(ContentModel.PROP_DESCRIPTION, update.getDescription()); + + properties.put(TransferModel.PROP_ENABLED, new Boolean(update.isEnabled())); + nodeService.setProperties(nodeRef, properties); + + TransferTargetImpl newTarget = new TransferTargetImpl(); + mapTransferTarget(nodeRef, newTarget); + return newTarget; + } + + /** + * + */ + public NodeRef transfer(String targetName, TransferDefinition definition) + { + final TransferEventProcessor eventProcessor = new TransferEventProcessor(); + return transferImpl(targetName, definition, eventProcessor); + } + + /** + * Transfer async. + * + * @param targetName + * @param definition + * @param callbacks + */ + public void transferAsync(String targetName, TransferDefinition definition, Set callbacks) + { + /** + * Event processor for this transfer instance + */ + final TransferEventProcessor eventProcessor = new TransferEventProcessor(); + if(callbacks != null) + { + eventProcessor.observers.addAll(callbacks); + } + + Map params = new HashMap(); + params.put("targetName", targetName); + params.put("definition", definition); + params.put("callbacks", (Serializable)callbacks); + + Action transferAction = getActionService().createAction("transfer-async", params); + + /** + * Execute transfer async in its own transaction. + * The action service only runs actions in the post commit which is why there's + * a separate transaction here. + */ + boolean success = false; + UserTransaction trx = transactionService.getNonPropagatingUserTransaction(); + try + { + trx.begin(); + logger.debug("calling action service to execute action"); + getActionService().executeAction(transferAction, null, false, true); + trx.commit(); + logger.debug("committed successfully"); + success = true; + } + catch (Exception error) + { + logger.error("unexpected exception", error); + throw new AlfrescoRuntimeException("unable to transfer async", error); + } + finally + { + if(!success) + { + try + { + logger.debug("rolling back after error"); + trx.rollback(); + } + catch (Exception error) + { + logger.error("unexpected exception during rollback", error); + // There's nothing much we can do here + } + } + } + } + + /** + * Transfer Synchronous + * @param targetName + * @param definition + * @param callbacks + */ + public NodeRef transfer(String targetName, TransferDefinition definition, Set callbacks) + { + /** + * Event processor for this transfer instance + */ + final TransferEventProcessor eventProcessor = new TransferEventProcessor(); + if(callbacks != null) + { + eventProcessor.observers.addAll(callbacks); + } + + /** + * Now go ahead and do the transfer + */ + return transferImpl(targetName, definition, eventProcessor); + } + + + /** + * Transfer Implementation + * @param targetName name of transfer target + * @param definition thranser definition + * @param eventProcessor + */ + private NodeRef transferImpl(String targetName, final TransferDefinition definition, final TransferEventProcessor eventProcessor) + { + NodeRef reportNode = null; + + if(logger.isDebugEnabled()) + { + logger.debug("transfer started to :" + targetName); + } + + /** + * Wire in the transferReport + */ + final List transferReport = new LinkedList(); + TransferCallback reportCallback = new TransferCallback() + { + private static final long serialVersionUID = 4072579605731036522L; + + public void processEvent(TransferEvent event) + { + transferReport.add(event); + } + }; + eventProcessor.addObserver(reportCallback); + + File snapshotFile = null; + TransferTarget target = null; + try + { + target = getTransferTarget(targetName); + + // which nodes to write to the snapshot + Setnodes = definition.getNodes(); + + if(nodes == null || nodes.size() == 0) + { + logger.debug("no nodes to transfer"); + throw new TransferException(MSG_NO_NODES); + } + + /** + * create snapshot + */ + String prefix = "TRX-SNAP"; + String suffix = ".xml"; + + long numberOfNodes = 0; + + logger.debug("create snapshot"); + + // where to put snapshot ? + snapshotFile = TempFileProvider.createTempFile(prefix, suffix); + + FileWriter snapshotWriter = new FileWriter(snapshotFile); + + // Write the manifest file + TransferManifestWriter formatter = new XMLTransferManifestWriter(); + TransferManifestHeader header = new TransferManifestHeader(); + header.setCreatedDate(new Date()); + formatter.startTransferManifest(snapshotWriter); + formatter.writeTransferManifestHeader(header); + for(NodeRef nodeRef : nodes) + { + TransferManifestNode node = transferManifestNodeFactory.createTransferManifestNode(nodeRef); + formatter.writeTransferManifestNode(node); + numberOfNodes++; + } + formatter.endTransferManifest(); + snapshotWriter.close(); + + logger.debug("snapshot file written to local filesystem"); + // If we are debugging then write the file to stdout. + if(logger.isDebugEnabled()) + { + try + { + outputFile(snapshotFile); + } + catch (Exception error) + { + error.printStackTrace(); + } + } + + /** + * Begin + */ + eventProcessor.start(); + final Transfer transfer = transmitter.begin(target); + if(transfer != null) + { + logger.debug("transfer begin"); + + boolean prepared = false; + try + { + /** + * send Manifest + */ + eventProcessor.sendManifest(1,1); + DeltaList deltas = transmitter.sendManifest(transfer, snapshotFile); + + logger.debug("manifest sent"); + + /** + * Parse the manifest file and transfer chunks over + */ + + // create a chunker and wire it up to the transmitter + final ContentChunker chunker = new ContentChunkerImpl(); + final Long fRange = Long.valueOf(numberOfNodes); + chunker.setHandler( + new ContentChunkProcessor(){ + private long counter = 0; + public void processChunk(Set data) + { + logger.debug("send chunk to transmitter"); + for(ContentData file : data) + { + counter++; + eventProcessor.sendContent(file, fRange, counter); + } + transmitter.sendContent(transfer, data); + } + } + ); + + + // create a manifest processor and wire it up to the chunker + TransferManifestProcessor processor = new TransferManifestProcessor() + { + public void processTransferManifestNode(TransferManifestNormalNode node) + { + Set data = TransferManifestNodeHelper.getContentData(node); + for(ContentData d : data) + { + logger.debug("add content to chunker"); + chunker.addContent(d); + } + } + + public void processTransferManifiestHeader(TransferManifestHeader header){/* NO-OP */ } + public void startTransferManifest(){ /* NO-OP */ } + public void endTransferManifest(){ /* NO-OP */ } + public void processTransferManifestNode(TransferManifestDeletedNode node) + { /* NO-OP */ + } + }; + + // wire up the manifest parser to a manifest processor + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + SAXParser parser; + parser = saxParserFactory.newSAXParser(); + XMLTransferManifestReader reader = new XMLTransferManifestReader(processor); + + // start the magic + parser.parse(snapshotFile, reader); + + chunker.flush(); + + logger.debug("content sending finished"); + + /** + * prepare + */ + eventProcessor.prepare(); + transmitter.prepare(transfer); + logger.debug("prepared"); + + /** + * committing + */ + eventProcessor.committing(100, 0); + transmitter.commit(transfer); + + /** + * need to poll for status + */ + // transmitter.getCommitStatus(transfer); + + eventProcessor.committing(100, 50); + eventProcessor.committing(100, 100); + + /** + * committed + */ + //eventProcessor.serverMessage("Committed"); + + eventProcessor.success(); + + prepared = true; + + logger.debug("committed"); + + // Write the Successful transfer report if we get here + // in its own transaction so it cannot be rolled back + final TransferTarget fTarget = target; + reportNode = transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + logger.debug("transfer report starting"); + NodeRef reportNode = transferReporter.createTransferReport(transfer, fTarget, definition, transferReport); + logger.debug("transfer report done"); + return reportNode; + } + }); + } + finally + { + if(!prepared) + { + logger.debug("abort incomplete transfer"); + transmitter.abort(transfer); + } + } + } + } + catch (TransferException t) + { + eventProcessor.error(t); + + /** + * Write the transfer report. This is an error report so needs to be out + */ + if(target != null && reportNode!= null) + { +// reportNode = transferReporter.createTransferReport(target, definition, transferReport); + } + throw t; + } + catch (Exception t) + { + // Wrap any other exception as a transfer exception + logger.debug("unable to transfer", t); + eventProcessor.error(t); + + /** + * Write the transfer report. This is an error report so needs to be out + */ + if(target != null && reportNode!= null) + { +// reportNode = transferReporter.createTransferReport(target, definition, transferReport); + } + throw new TransferException("unable to transfer:" + t.toString(), t); + } + finally + { + /** + * clean up + */ + if(snapshotFile != null) + { + snapshotFile.delete(); + } + } + + return reportNode; + } // end of transferImpl + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public NodeService getNodeService() + { + return nodeService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public SearchService getSearchService() + { + return searchService; + } + + public void setTransferSpaceQuery(String transferSpaceQuery) + { + this.transferSpaceQuery = transferSpaceQuery; + } + + public String getTransferSpaceQuery() + { + return transferSpaceQuery; + } + + public void setDefaultTransferGroup(String defaultGroup) + { + this.defaultTransferGroup = defaultGroup; + } + + public String getDefaultTransferGroup() + { + return defaultTransferGroup; + } + + public TransferTransmitter getTransmitter() + { + return transmitter; + } + + public void setTransmitter(TransferTransmitter transmitter) + { + this.transmitter = transmitter; + } + + private NodeRef transferHome; + protected NodeRef getTransferHome() + { + if(transferHome == null) + { + String query = transferSpaceQuery; + + ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, + SearchService.LANGUAGE_XPATH, query); + + if(result.length() == 0) + { + // No transfer home. + throw new TransferException(MSG_NO_HOME, new Object[]{query}); + } + if (result.getNodeRefs().size() != 0) + { + transferHome = result.getNodeRef(0); + } + } + return transferHome; + } + + private char[] encrypt(char[] text) + { + // placeholder dummy implementation - add an 'E' to the start +// String dummy = new String("E" + text); +// String dummy = new String(text); +// return dummy.toCharArray(); + return text; + } + + private char[] decrypt(char[] text) + { + // placeholder dummy implementation - strips off leading 'E' +// String dummy = new String(text); + return text; + //return dummy.substring(1).toCharArray(); + } + + /** + * + * @param name + * @return + */ + private NodeRef lookupTransferTarget(String name) + { + String query = "+TYPE:\"trx:transferTarget\" +@cm\\:name:\"" +name + "\""; + + ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, + SearchService.LANGUAGE_LUCENE, query); + + if(result.length() == 1) + { + return result.getNodeRef(0); + } + return null; + } + + private void mapTransferTarget(NodeRef nodeRef, TransferTargetImpl def) + { + def.setNodeRef(nodeRef); + Map properties = nodeService.getProperties(nodeRef); + def.setEndpointPath((String)properties.get(TransferModel.PROP_ENDPOINT_PATH)); + def.setEndpointProtocol((String)properties.get(TransferModel.PROP_ENDPOINT_PROTOCOL)); + def.setEndpointHost((String)properties.get(TransferModel.PROP_ENDPOINT_HOST)); + def.setEndpointPort((Integer)properties.get(TransferModel.PROP_ENDPOINT_PORT)); + Serializable passwordVal = properties.get(TransferModel.PROP_PASSWORD); + + if(passwordVal.getClass().isArray()) + { + def.setPassword(decrypt((char[])passwordVal)); + } + if(passwordVal instanceof String) + { + String password = (String)passwordVal; + def.setPassword(decrypt(password.toCharArray())); + } + + + def.setUsername((String)properties.get(TransferModel.PROP_USERNAME)); + def.setName((String)properties.get(ContentModel.PROP_NAME)); + def.setTitle((String)properties.get(ContentModel.PROP_TITLE)); + def.setDescription((String)properties.get(ContentModel.PROP_DESCRIPTION)); + + if(nodeService.hasAspect(nodeRef, TransferModel.ASPECT_ENABLEABLE)) + { + def.setEnabled((Boolean)properties.get(TransferModel.PROP_ENABLED)); + } + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.transfer.TransferService#verify(org.alfresco.service.cmr.transfer.TransferTarget) + */ + public void verify(TransferTarget target) throws TransferException + { + transmitter.verifyTarget(target); + } + + /** + * Utility to dump the contents of a file to the console + * @param file + */ + private static void outputFile(File file) throws Exception + { + BufferedReader reader = new BufferedReader(new FileReader(file)); + String s = reader.readLine(); + while(s != null) + { + System.out.println(s); + s = reader.readLine(); + } + } + + public void setTransferManifestNodeFactory(TransferManifestNodeFactory transferManifestNodeFactory) + { + this.transferManifestNodeFactory = transferManifestNodeFactory; + } + + public TransferManifestNodeFactory getTransferManifestNodeFactory() + { + return transferManifestNodeFactory; + } + + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + public ActionService getActionService() + { + return actionService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public TransactionService getTransactionService() + { + return transactionService; + } + + public void setTransferReporter(TransferReporter transferReporter) + { + this.transferReporter = transferReporter; + } + + public TransferReporter getTransferReporter() + { + return transferReporter; + } +} diff --git a/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java b/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java new file mode 100644 index 0000000000..eaf42f00b6 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferServiceImplTest.java @@ -0,0 +1,1265 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Queue; +import java.util.Set; + +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.transfer.manifest.TransferManifestNodeFactory; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.transfer.TransferCallback; +import org.alfresco.service.cmr.transfer.TransferDefinition; +import org.alfresco.service.cmr.transfer.TransferEvent; +import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.cmr.transfer.TransferReceiver; +import org.alfresco.service.cmr.transfer.TransferService; +import org.alfresco.service.cmr.transfer.TransferTarget; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.BaseAlfrescoSpringTest; +import org.alfresco.util.Pair; + +/** + * Unit test for TransferServiceImpl + * + * Contains some unit tests for the transfer definitions. + * + * @author Mark Rogers + */ +public class TransferServiceImplTest extends BaseAlfrescoSpringTest +{ + private TransferService transferService; + private ContentService contentService; + private TransferServiceImpl transferServiceImpl; + private SearchService searchService; + private TransactionService transactionService; + private TransferReceiver receiver; + private TransferManifestNodeFactory transferManifestNodeFactory; + + String COMPANY_HOME_XPATH_QUERY = "/{http://www.alfresco.org/model/application/1.0}company_home"; + String GUEST_HOME_XPATH_QUERY = "/{http://www.alfresco.org/model/application/1.0}company_home/{http://www.alfresco.org/model/application/1.0}guest_home"; + + + /** + * Called during the transaction setup + */ + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + + // Get the required services + this.transferService = (TransferService)this.applicationContext.getBean("TransferService"); + this.contentService = (ContentService)this.applicationContext.getBean("ContentService"); + this.transferServiceImpl = (TransferServiceImpl)this.applicationContext.getBean("transferService"); + this.searchService = (SearchService)this.applicationContext.getBean("SearchService"); + this.transactionService = (TransactionService)this.applicationContext.getBean("TransactionService"); + this.receiver = (TransferReceiver)this.applicationContext.getBean("transferReceiver"); + this.transferManifestNodeFactory = (TransferManifestNodeFactory)this.applicationContext.getBean("transferManifestNodeFactory"); + + assertNotNull("receiver is null", this.receiver); + + } + + /** + * Test create target. + * + * @throws Exception + */ + public void testCreateTarget() throws Exception + { + String name = "name"; + String title = "title"; + String description = "description"; + String endpointProtocol = "http"; + String endpointHost = "localhost"; + int endpointPort = 8080; + String endpointPath = "rhubarb"; + String username = "admin"; + char[] password = "password".toCharArray(); + + /** + * Now go ahead and create our first transfer target + */ + TransferTarget ret = transferService.createTransferTarget(name, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + assertNotNull("return value is null", ret); + assertNotNull("node ref is null", ret.getNodeRef()); + + //titled aspect + assertEquals("name not equal", ret.getName(), name); + assertEquals("title not equal", ret.getTitle(), title); + assertEquals("description not equal", ret.getDescription(), description); + + // endpoint + assertEquals("endpointProtocol not equal", ret.getEndpointProtocol(), endpointProtocol); + assertEquals("endpointHost not equal", ret.getEndpointHost(), endpointHost); + assertEquals("endpointPort not equal", ret.getEndpointPort(), endpointPort); + assertEquals("endpointPath not equal", ret.getEndpointPath(), endpointPath); + + // authentication + assertEquals("username not equal", ret.getUsername(), username); + char[] password2 = ret.getPassword(); + + assertEquals(password.length, password2.length); + + for(int i = 0; i < password.length; i++) + { + if(password[i] != password2[i]) + { + fail("password not equal:" + new String(password) + new String(password2)); + } + } + + /** + * Negative test - try to create a transfer target with a name that's already used. + */ + try + { + transferService.createTransferTarget(name, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + fail("duplicate name not detected"); + } + catch (TransferException e) + { + // expect to go here + } + } + + /** + * Test of Get TransferTargets + * + * @throws Exception + */ + public void testGetTransferTargets() throws Exception + { + String nameA = "nameA"; + String nameB = "nameB"; + String title = "title"; + String description = "description"; + String endpointProtocol = "http"; + String endpointHost = "localhost"; + int endpointPort = 8080; + String endpointPath = "rhubarb"; + String username = "admin"; + char[] password = "password".toCharArray(); + + /** + * Now go ahead and create our first transfer target + */ + transferService.createTransferTarget(nameA, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + transferService.createTransferTarget(nameB, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + + Set targets = transferService.getTransferTargets(); + assertTrue("targets is empty", targets.size() > 0); + assertEquals("targets is empty", targets.size(), 2); + for(TransferTarget target : targets) + { + System.out.println("found target"); + } + } + + /** + * Test of Get All Trabsfer Targets By Group + */ + //TODO Test not complete - can't yet put targets in different groups + public void testGetAllTransferTargetsByGroup() throws Exception + { + String getMe = "getMe"; + String title = "title"; + String description = "description"; + String endpointProtocol = "http"; + String endpointHost = "localhost"; + int endpointPort = 8080; + String endpointPath = "rhubarb"; + String username = "admin"; + char[] password = "password".toCharArray(); + + /** + * Now go ahead and create our first transfer target + */ + transferService.createTransferTarget(getMe, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + + Set targets = transferService.getTransferTargets("Default Group"); + assertTrue("targets is empty", targets.size() > 0); + + /** + * Negative test - group does not exist + */ + try + { + transferService.getTransferTargets("Rubbish"); + assertTrue("targets is empty", targets.size() > 0); + fail("group does not exist"); + } + catch (TransferException te) + { + // expect to go here + } + } + + /** + * + */ + public void testUpdateTransferTarget() throws Exception + { + String updateMe = "updateMe"; + String title = "title"; + String description = "description"; + String endpointProtocol = "http"; + String endpointHost = "localhost"; + int endpointPort = 8080; + String endpointPath = "rhubarb"; + String username = "admin"; + char[] password = "password".toCharArray(); + + + /** + * Create our transfer target + */ + TransferTarget target = transferService.createTransferTarget(updateMe, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + + /* + * Now update with exactly the same values. + */ + TransferTarget update1 = transferService.updateTransferTarget(target); + + assertNotNull("return value is null", update1); + assertNotNull("node ref is null", update1.getNodeRef()); + + //titled aspect + assertEquals("name not equal", update1.getName(), updateMe); + assertEquals("title not equal", update1.getTitle(), title); + assertEquals("description not equal", update1.getDescription(), description); + + // endpoint + assertEquals("endpointProtocol not equal", update1.getEndpointProtocol(), endpointProtocol); + assertEquals("endpointHost not equal", update1.getEndpointHost(), endpointHost); + assertEquals("endpointPort not equal", update1.getEndpointPort(), endpointPort); + assertEquals("endpointPath not equal", update1.getEndpointPath(), endpointPath); + + // authentication + assertEquals("username not equal", update1.getUsername(), username); + char[] pass = update1.getPassword(); + + assertEquals(password.length, pass.length); + + for(int i = 0; i < password.length; i++) + { + if(password[i] != pass[i]) + { + fail("password not equal:" + new String(password) + new String(pass)); + } + } + + /** + * Now update with different values + */ + String title2 = "Two"; + String description2 = "descriptionTwo"; + String endpointProtocol2 = "https"; + String endpointHost2 = "1.0.0.127"; + int endpointPort2 = 4040; + String endpointPath2 = "custard"; + String username2 = "admin_two"; + char[] password2 = "two".toCharArray(); + + target.setDescription(description2); + target.setTitle(title2); + target.setEndpointHost(endpointHost2); + target.setEndpointPath(endpointPath2); + target.setEndpointPort(endpointPort2); + target.setEndpointProtocol(endpointProtocol2); + target.setName(updateMe); + target.setPassword(password2); + target.setUsername(username2); + + TransferTarget update2 = transferService.updateTransferTarget(target); + assertNotNull("return value is null", update2); + assertNotNull("node ref is null", update2.getNodeRef()); + + //titled aspect + assertEquals("name not equal", update2.getName(), updateMe); + assertEquals("title not equal", update2.getTitle(), title2); + assertEquals("description not equal", update2.getDescription(), description2); + + // endpoint + assertEquals("endpointProtocol not equal", update2.getEndpointProtocol(), endpointProtocol2); + assertEquals("endpointHost not equal", update2.getEndpointHost(), endpointHost2); + assertEquals("endpointPort not equal", update2.getEndpointPort(), endpointPort2); + assertEquals("endpointPath not equal", update2.getEndpointPath(), endpointPath2); + + // authentication + assertEquals("username not equal", update2.getUsername(), username2); + pass = update2.getPassword(); + + assertEquals(password2.length, pass.length); + + for(int i = 0; i < pass.length; i++) + { + if(pass[i] != password2[i]) + { + fail("password not equal:" + new String(pass) + new String(password2)); + } + } + + } + + /** + * + */ + public void testDeleteTransferTarget() throws Exception + { + String deleteMe = "deleteMe"; + String title = "title"; + String description = "description"; + String endpointProtocol = "http"; + String endpointHost = "localhost"; + int endpointPort = 8080; + String endpointPath = "rhubarb"; + String username = "admin"; + char[] password = "password".toCharArray(); + + /** + * Now go ahead and create our first transfer target + */ + transferService.createTransferTarget(deleteMe, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + + transferService.deleteTransferTarget(deleteMe); + + /** + * Negative test - try to delete the transfer target we deleted above. + */ + try + { + transferService.deleteTransferTarget(deleteMe); + fail("duplicate name not detected"); + } + catch (TransferException e) + { + // expect to go here + } + + /** + * Negative test - try to delete a transfer target that has never existed + */ + try + { + transferService.deleteTransferTarget("rubbish"); + + fail("rubbish deleted"); + } + catch (TransferException e) + { + // expect to go here + } + } + + public void testEnableTransferTarget() throws Exception + { + String targetName = "enableMe"; + + /** + * Now go ahead and create our first transfer target + */ + TransferTarget enableMe = createTransferTarget(targetName); + try + { + /** + * Check a new target is enabled + */ + TransferTarget target = transferService.getTransferTarget(targetName); + assertTrue("new target is not enabled", enableMe.isEnabled()); + + /** + * Diasble the target + */ + transferService.enableTransferTarget(targetName, false); + target = transferService.getTransferTarget(targetName); + assertFalse("target is not disabled", target.isEnabled()); + + /** + * Now re-enable the target + */ + transferService.enableTransferTarget(targetName, true); + target = transferService.getTransferTarget(targetName); + assertTrue("re-enabled target is not enabled", target.isEnabled()); + } + finally + { + transferService.deleteTransferTarget(targetName); + } + } + + /** + * Test the transfer method by sending one node. + * + * This is a unit test so it does some shenanigans to send to the same instance of alfresco. + */ + public void testTransferOneNode() throws Exception + { + + ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, COMPANY_HOME_XPATH_QUERY); + NodeRef companyHome = result.getNodeRef(0); + + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + /** + * Create a test node that we will read and write + */ + String CONTENT_TITLE = "ContentTitle"; + String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; + String CONTENT_NAME = "Demo Node 1"; + Locale CONTENT_LOCALE = Locale.GERMAN; + String CONTENT_STRING = "Hello"; + + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName("testNode"), ContentModel.TYPE_CONTENT); + NodeRef contentNodeRef = child.getChildRef(); + nodeService.setProperty(contentNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(contentNodeRef, ContentModel.PROP_NAME, CONTENT_NAME); + + ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + + /** + * For unit test + * - replace the HTTP transport with the in-process transport + * - replace the node factory with one that will map node refs, paths etc. + */ + TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(this.receiver, this.contentService); + transferServiceImpl.setTransmitter(transmitter); + UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + List> pathMap = testNodeFactory.getPathMap(); + // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. + pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); + + /** + * Now go ahead and create our first transfer target + */ + String targetName = "testTransferOneNodeNoContent"; + TransferTarget transferMe = createTransferTarget(targetName); + try + { + /** + * Transfer our transfer target node which has no content + */ + { + TransferDefinition definition = new TransferDefinition(); + Setnodes = new HashSet(); + nodes.add(contentNodeRef); + definition.setNodes(nodes); + transferService.transfer(targetName, definition, null); + } + + // Now validate that the target node exists and has similar properties to the source + NodeRef destNodeRef = testNodeFactory.getMappedNodeRef(contentNodeRef); + assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); + assertTrue("dest node ref does not exist", nodeService.exists(destNodeRef)); + assertEquals("title is wrong", (String)nodeService.getProperty(destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE); + assertEquals("type is wrong", nodeService.getType(contentNodeRef), nodeService.getType(destNodeRef)); + + nodeService.setProperty(contentNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE_UPDATED); + + /** + * Transfer our node again - so this is an update + */ + { + TransferDefinition definition = new TransferDefinition(); + Setnodes = new HashSet(); + nodes.add(contentNodeRef); + definition.setNodes(nodes); + transferService.transfer(targetName, definition, null); + } + + // Now validate that the target node exists and has similar properties to the source + assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); + assertTrue("dest node ref does not exist", nodeService.exists(destNodeRef)); + assertEquals("title is wrong", (String)nodeService.getProperty(destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE_UPDATED); + assertEquals("type is wrong", nodeService.getType(contentNodeRef), nodeService.getType(destNodeRef)); + + /** + * Negative test transfer nothing + */ + try + { + TransferDefinition definition = new TransferDefinition(); + transferService.transfer(targetName, definition, null); + fail("exception not thrown"); + } + catch(TransferException te) + { + // expect to go here + } + } + finally + { + //transferService.deleteTransferTarget(targetName); + } + } + + /** + * Test the transfer method by sending a graph of node. + * + * This is a unit test so it does some shenanigans to send to he same instance of alfresco. + */ + public void testManyNodes() throws Exception + { + + ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, COMPANY_HOME_XPATH_QUERY); + NodeRef companyHome = result.getNodeRef(0); + + Setnodes = new HashSet(); + + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + /** + * Create a test node that we will read and write + */ + String CONTENT_TITLE = "ContentTitle"; + String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; + String CONTENT_NAME = "Demo Node 1"; + Locale CONTENT_LOCALE = Locale.GERMAN; + String CONTENT_STRING = "The quick brown fox"; + + /** + * Create a tree + * A + * --AA + * --AB + * ----ABA + * -------- 100+ nodes + * ----ABB + * ----ABC + * B + */ + + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName("testNodeA"), ContentModel.TYPE_CONTENT); + NodeRef nodeA = child.getChildRef(); + nodeService.setProperty(nodeA , ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(nodeA , ContentModel.PROP_NAME, CONTENT_NAME); + + { + ContentWriter writer = contentService.getWriter(nodeA , ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + nodes.add(nodeA); + } + + child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName("testNodeB"), ContentModel.TYPE_CONTENT); + NodeRef nodeB = child.getChildRef(); + nodeService.setProperty(nodeA , ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(nodeA , ContentModel.PROP_NAME, CONTENT_NAME); + nodes.add(nodeB); + + child = nodeService.createNode(nodeA, ContentModel.ASSOC_CONTAINS, QName.createQName("testNodeAA"), ContentModel.TYPE_CONTENT); + NodeRef nodeAA = child.getChildRef(); + nodeService.setProperty(nodeA , ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(nodeA , ContentModel.PROP_NAME, CONTENT_NAME); + nodes.add(nodeAA); + + child = nodeService.createNode(nodeA, ContentModel.ASSOC_CONTAINS, QName.createQName("testNodeAB"), ContentModel.TYPE_CONTENT); + NodeRef nodeAB = child.getChildRef(); + nodeService.setProperty(nodeA , ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(nodeA , ContentModel.PROP_NAME, CONTENT_NAME); + nodes.add(nodeAB); + + child = nodeService.createNode(nodeAB, ContentModel.ASSOC_CONTAINS, QName.createQName("testNodeABA"), ContentModel.TYPE_CONTENT); + NodeRef nodeABA = child.getChildRef(); + nodeService.setProperty(nodeA , ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(nodeA , ContentModel.PROP_NAME, CONTENT_NAME); + nodes.add(nodeABA); + + child = nodeService.createNode(nodeAB, ContentModel.ASSOC_CONTAINS, QName.createQName("testNodeABB"), ContentModel.TYPE_CONTENT); + NodeRef nodeABB = child.getChildRef(); + nodeService.setProperty(nodeA , ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(nodeA , ContentModel.PROP_NAME, CONTENT_NAME); + nodes.add(nodeABB); + + child = nodeService.createNode(nodeAB, ContentModel.ASSOC_CONTAINS, QName.createQName("testNodeABC"), ContentModel.TYPE_CONTENT); + NodeRef nodeABC = child.getChildRef(); + nodeService.setProperty(nodeA , ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(nodeA , ContentModel.PROP_NAME, CONTENT_NAME); + nodes.add(nodeABC); + + /** + * For unit test + * - replace the HTTP transport with the in-process transport + * - replace the node factory with one that will map node refs, paths etc. + */ + TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(this.receiver, this.contentService); + transferServiceImpl.setTransmitter(transmitter); + UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + List> pathMap = testNodeFactory.getPathMap(); + // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. + pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); + + /** + * Now go ahead and create our first transfer target + */ + String targetName = "testManyNodes"; + TransferTarget transferMe = createTransferTarget(targetName); + + try + { + /** + * Transfer our transfer target nodes + */ + { + TransferDefinition definition = new TransferDefinition(); + definition.setNodes(nodes); + transferService.transfer(targetName, definition, null); + } + + // Now validate that the target node exists and has similar properties to the source + NodeRef destNodeA = testNodeFactory.getMappedNodeRef(nodeA); + assertFalse("unit test stuffed up - comparing with self", destNodeA.equals(transferMe.getNodeRef())); + assertTrue("dest node ref A does not exist", nodeService.exists(destNodeA)); + assertEquals("title is wrong", (String)nodeService.getProperty(destNodeA, ContentModel.PROP_TITLE), CONTENT_TITLE); + assertEquals("type is wrong", nodeService.getType(nodeA), nodeService.getType(destNodeA)); + + NodeRef destNodeB = testNodeFactory.getMappedNodeRef(nodeB); + assertTrue("dest node B does not exist", nodeService.exists(destNodeB)); + + NodeRef destNodeAA = testNodeFactory.getMappedNodeRef(nodeAA); + assertTrue("dest node AA ref does not exist", nodeService.exists(destNodeAA)); + + NodeRef destNodeAB = testNodeFactory.getMappedNodeRef(nodeAB); + assertTrue("dest node AB ref does not exist", nodeService.exists(destNodeAB)); + + NodeRef destNodeABA = testNodeFactory.getMappedNodeRef(nodeABA); + assertTrue("dest node ABA ref does not exist", nodeService.exists(destNodeABA)); + + NodeRef destNodeABB = testNodeFactory.getMappedNodeRef(nodeABB); + assertTrue("dest node ABB ref does not exist", nodeService.exists(destNodeABB)); + + NodeRef destNodeABC = testNodeFactory.getMappedNodeRef(nodeABC); + assertTrue("dest node ABC ref does not exist", nodeService.exists(destNodeABC)); + + /** + * Update a single node (NodeAB) from the middle of the tree + */ + { + nodeService.setProperty(nodeAB , ContentModel.PROP_TITLE, CONTENT_TITLE_UPDATED); + + TransferDefinition definition = new TransferDefinition(); + SettoUpdate = new HashSet(); + toUpdate.add(nodeAB); + definition.setNodes(toUpdate); + transferService.transfer(targetName, definition, null); + + assertEquals("title is wrong", (String)nodeService.getProperty(destNodeAB, ContentModel.PROP_TITLE), CONTENT_TITLE_UPDATED); + } + + /** + * Now generate a large number of nodes + */ + + for(int i = 0; i < 100; i++) + { + child = nodeService.createNode(nodeABA, ContentModel.ASSOC_CONTAINS, QName.createQName("testNode" + i), ContentModel.TYPE_CONTENT); + + NodeRef nodeX = child.getChildRef(); + nodeService.setProperty(nodeX , ContentModel.PROP_TITLE, CONTENT_TITLE + i); + nodeService.setProperty(nodeX , ContentModel.PROP_NAME, CONTENT_NAME +i); + nodes.add(nodeX); + + ContentWriter writer = contentService.getWriter(nodeX, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING + i); + } + + /** + * Transfer our transfer target nodes + */ + { + TransferDefinition definition = new TransferDefinition(); + definition.setNodes(nodes); + transferService.transfer(targetName, definition, null); + } + + + } + finally + { + transferService.deleteTransferTarget(targetName); + } + } // end many nodes + + + /** + * Test the path based update. + * + * This is a unit test so it does some shenanigans to send to the same instance of alfresco. + */ + public void testPathBasedUpdate() throws Exception + { + + ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, COMPANY_HOME_XPATH_QUERY); + NodeRef companyHome = result.getNodeRef(0); + + QName TEST_QNAME = QName.createQName("testPathBasedUpdate"); + + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + /** + * Create a test node that we will transfer. Its path is what is important + */ + String CONTENT_TITLE = "ContentTitle"; + String CONTENT_TITLE_UPDATED = "ContentTitleUpdated"; + String CONTENT_NAME = "Demo Node 1"; + Locale CONTENT_LOCALE = Locale.GERMAN; + String CONTENT_STRING = "Hello"; + + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, TEST_QNAME, ContentModel.TYPE_CONTENT); + NodeRef contentNodeRef = child.getChildRef(); + nodeService.setProperty(contentNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(contentNodeRef, ContentModel.PROP_NAME, CONTENT_NAME); + + ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + + /** + * For unit test + * - replace the HTTP transport with the in-process transport + * - replace the node factory with one that will map node refs, paths etc. + */ + TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(this.receiver, this.contentService); + transferServiceImpl.setTransmitter(transmitter); + UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + List> pathMap = testNodeFactory.getPathMap(); + // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. + pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); + + /** + * Now go ahead and create our transfer target + */ + String targetName = "testPathBasedUpdate"; + TransferTarget transferMe = createTransferTarget(targetName); + try + { + /** + * Transfer our transfer target node + */ + { + TransferDefinition definition = new TransferDefinition(); + Setnodes = new HashSet(); + nodes.add(contentNodeRef); + definition.setNodes(nodes); + transferService.transfer(targetName, definition, null); + } + + // Now validate that the target node exists and has similar properties to the source + NodeRef destNodeRef = testNodeFactory.getMappedNodeRef(contentNodeRef); + assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); + assertTrue("dest node ref does not exist", nodeService.exists(destNodeRef)); + assertEquals("title is wrong", (String)nodeService.getProperty(destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE); + assertEquals("type is wrong", nodeService.getType(contentNodeRef), nodeService.getType(destNodeRef)); + + /** + * Now delete the content node and re-create another one with the old path + */ + nodeService.deleteNode(contentNodeRef); + + child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, TEST_QNAME, ContentModel.TYPE_CONTENT); + contentNodeRef = child.getChildRef(); + nodeService.setProperty(contentNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE_UPDATED); + + /** + * Transfer our node which is a new node (so will not exist on the back end) with a path that already has a node. + */ + { + TransferDefinition definition = new TransferDefinition(); + Setnodes = new HashSet(); + nodes.add(contentNodeRef); + definition.setNodes(nodes); + transferService.transfer(targetName, definition, null); + } + + // Now validate that the target node exists and has been updated by the node with the new node ref + assertFalse("unit test stuffed up - comparing with self", destNodeRef.equals(transferMe.getNodeRef())); + assertTrue("dest node ref does not exist", nodeService.exists(destNodeRef)); + assertEquals("title is wrong", (String)nodeService.getProperty(destNodeRef, ContentModel.PROP_TITLE), CONTENT_TITLE_UPDATED); + assertEquals("type is wrong", nodeService.getType(contentNodeRef), nodeService.getType(destNodeRef)); + + } + finally + { + transferService.deleteTransferTarget(targetName); + } + } // Path based update + + + /** + * Test the transfer method when it is running async. + * + * This is a unit test so it does some shenanigans to send to the same instance of alfresco. + */ + public void testAsyncCallback() throws Exception + { + int MAX_SLEEPS = 5; + + ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, COMPANY_HOME_XPATH_QUERY); + NodeRef companyHome = result.getNodeRef(0); + + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + /** + * For unit test + * - replace the HTTP transport with the in-process transport + * - replace the node factory with one that will map node refs, paths etc. + */ + TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(this.receiver, this.contentService); + transferServiceImpl.setTransmitter(transmitter); + UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + List> pathMap = testNodeFactory.getPathMap(); + // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. + pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); + + /** + * Now go ahead and create our first transfer target + * This needs to be committed before we can call transfer asycnc. + */ + String CONTENT_TITLE = "ContentTitle"; + String CONTENT_NAME_A = "Demo Node A"; + String CONTENT_NAME_B = "Demo Node B"; + Locale CONTENT_LOCALE = Locale.GERMAN; + String CONTENT_STRING = "Hello"; + + NodeRef nodeRefA = null; + NodeRef nodeRefB = null; + String targetName = "testAsyncCallback"; + { + UserTransaction trx = transactionService.getNonPropagatingUserTransaction(); + trx.begin(); + try + { + nodeRefA = nodeService.getChildByName(guestHome, ContentModel.ASSOC_CONTAINS, CONTENT_NAME_A); + + if(nodeRefA == null) + { + /** + * Create a test node that we will read and write + */ + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName("testAsycNodeA"), ContentModel.TYPE_CONTENT); + nodeRefA = child.getChildRef(); + nodeService.setProperty(nodeRefA, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(nodeRefA, ContentModel.PROP_NAME, CONTENT_NAME_A); + + ContentWriter writer = contentService.getWriter(nodeRefA, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + + nodeRefB = nodeService.getChildByName(guestHome, ContentModel.ASSOC_CONTAINS, CONTENT_NAME_B); + + if(nodeRefB == null) + { + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName("testAsyncNodeB"), ContentModel.TYPE_CONTENT); + nodeRefB = child.getChildRef(); + nodeService.setProperty(nodeRefB, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(nodeRefB, ContentModel.PROP_NAME, CONTENT_NAME_B); + + ContentWriter writer = contentService.getWriter(nodeRefB, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + + TransferTarget transferMe = createTransferTarget(targetName); + } + finally + { + trx.commit(); + } + } + + /** + * The transfer report is a plain report of the transfer - no async shenanigans to worry about + */ + ListtransferReport = new ArrayList(50); + + try + { + /** + * Call the transferAsync method. + */ + { + TestTransferCallback callback = new TestTransferCallback(); + Set callbacks = new HashSet(); + callbacks.add(callback); + TransferDefinition definition = new TransferDefinition(); + Setnodes = new HashSet(); + nodes.add(nodeRefA); + nodes.add(nodeRefB); + definition.setNodes(nodes); + + transferService.transferAsync(targetName, definition, callbacks); + logger.debug("transfer async has returned"); + + /** + * Need to poll the transfer events here until callback receives the last event + */ + Queue events = callback.getEvents(); + + int sleepCount = MAX_SLEEPS; + boolean ended = false; + + TransferEvent event = events.poll(); + while (!ended) + { + logger.debug("polling loop:" + sleepCount); + + while(event != null) + { + /** + * Got an event - reset the sleep counter + */ + sleepCount = MAX_SLEEPS; + + logger.debug("Got an event" + event.toString()); + + /** + * Squirrel away the event for analysis later + */ + transferReport.add(event); + + /** + * If we read the last record which will either be SUCCESS or ERROR then we we have finished + */ + if(event.isLast()) + { + logger.debug("got last event"); + ended = true; + } + + /** + * Try to get the next event + */ + event = events.poll(); + } + + if(event == null && !ended) + { + if(sleepCount <= 0) + { + fail("timed out without receiving last event"); + ended = true; + } + else + { + /** + * No content - go to sleep to wait for some more + */ + if(sleepCount-- > 0) + { + // Sleep for 5 second + Thread.sleep(5000); + } + } + + /** + * Try to get the next event + */ + event = events.poll(); + } + } + } + + /** + * Now validate the transferReport + */ + assertTrue("transfer report is too small", transferReport.size() > 2); + assertTrue("transfer report does not start with START", transferReport.get(0).getTransferState().equals(TransferEvent.TransferState.START)); + assertTrue("transfer report does not end with SUCCESS", transferReport.get(transferReport.size()-1).getTransferState().equals(TransferEvent.TransferState.SUCCESS)); + } + finally + { + UserTransaction trx = transactionService.getNonPropagatingUserTransaction(); + trx.begin(); + transferService.deleteTransferTarget(targetName); + trx.commit(); + } + } // test async callback + + /** + * Test the transfer report. + * + * This is a unit test so it does some shenanigans to send to the same instance of alfresco. + */ + public void testTransferReport() throws Exception + { + + ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, COMPANY_HOME_XPATH_QUERY); + NodeRef companyHome = result.getNodeRef(0); + + /** + * Get guest home + */ + String guestHomeQuery = "/app:company_home/app:guest_home"; + ResultSet guestHomeResult = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); + assertEquals("", 1, guestHomeResult.length()); + NodeRef guestHome = guestHomeResult.getNodeRef(0); + + /** + * For unit test + * - replace the HTTP transport with the in-process transport + * - replace the node factory with one that will map node refs, paths etc. + */ + TransferTransmitter transmitter = new UnitTestInProcessTransmitterImpl(this.receiver, this.contentService); + transferServiceImpl.setTransmitter(transmitter); + UnitTestTransferManifestNodeFactory testNodeFactory = new UnitTestTransferManifestNodeFactory(this.transferManifestNodeFactory); + transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory); + List> pathMap = testNodeFactory.getPathMap(); + // Map company_home/guest_home to company_home so tranferred nodes and moved "up" one level. + pathMap.add(new Pair(PathHelper.stringToPath(GUEST_HOME_XPATH_QUERY), PathHelper.stringToPath(COMPANY_HOME_XPATH_QUERY))); + + /** + * Now go ahead and create our first transfer target + * This needs to be committed before we can call transfer asycnc. + */ + String CONTENT_TITLE = "ContentTitle"; + String CONTENT_NAME_A = "Demo Node A"; + String CONTENT_NAME_B = "Demo Node B"; + Locale CONTENT_LOCALE = Locale.GERMAN; + String CONTENT_STRING = "Hello"; + + NodeRef nodeRefA = null; + NodeRef nodeRefB = null; + TransferTarget transferMe = null; + String targetName = "testTransferReport"; + + nodeRefA = nodeService.getChildByName(guestHome, ContentModel.ASSOC_CONTAINS, CONTENT_NAME_A); + + if(nodeRefA == null) + { + /** + * Create a test node that we will read and write + */ + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName("testAsycNodeA"), ContentModel.TYPE_CONTENT); + nodeRefA = child.getChildRef(); + nodeService.setProperty(nodeRefA, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(nodeRefA, ContentModel.PROP_NAME, CONTENT_NAME_A); + + ContentWriter writer = contentService.getWriter(nodeRefA, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + + nodeRefB = nodeService.getChildByName(guestHome, ContentModel.ASSOC_CONTAINS, CONTENT_NAME_B); + + if(nodeRefB == null) + { + ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName("testAsyncNodeB"), ContentModel.TYPE_CONTENT); + nodeRefB = child.getChildRef(); + nodeService.setProperty(nodeRefB, ContentModel.PROP_TITLE, CONTENT_TITLE); + nodeService.setProperty(nodeRefB, ContentModel.PROP_NAME, CONTENT_NAME_B); + + ContentWriter writer = contentService.getWriter(nodeRefB, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + } + + transferMe = createTransferTarget(targetName); + + /** + * Call the transfer method. + */ + { + TestTransferCallback callback = new TestTransferCallback(); + Set callbacks = new HashSet(); + callbacks.add(callback); + TransferDefinition definition = new TransferDefinition(); + Setnodes = new HashSet(); + nodes.add(nodeRefA); + nodes.add(nodeRefB); + definition.setNodes(nodes); + + NodeRef transferReport = transferService.transfer(targetName, definition, callbacks); + assertNotNull("transfer report is null", transferReport); + + ContentReader reader = contentService.getReader(transferReport, ContentModel.PROP_CONTENT); + assertNotNull("transfer reader is null", reader); + + logger.debug("now show the contents of the transfer report"); + reader.getContent(System.out); + } + + logger.debug("now delete the target:" + targetName); + + transferService.deleteTransferTarget(targetName); + + } // test transfer report + + + + +// /** +// * Test the transfer method with big content - commented out since it takes a long time to run. +// */ +// public void testTransferOneNodeWithBigContent() throws Exception +// { +// String CONTENT_TITLE = "ContentTitle"; +// String CONTENT_NAME = "Demo Node 6"; +// Locale CONTENT_LOCALE = Locale.GERMAN; +// String CONTENT_STRING = "Hello"; +// +// String targetName = "testTransferOneNodeWithBigContent"; +// +// String guestHomeQuery = "/app:company_home/app:guest_home"; +// ResultSet result = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, guestHomeQuery); +// +// assertEquals("", 1, result.length()); +// NodeRef guestHome = result.getNodeRef(0); +// ChildAssociationRef childAssoc = result.getChildAssocRef(0); +// System.out.println("Guest home:" + guestHome); +// assertNotNull(guestHome); +// +// /** +// * Now go ahead and create our first transfer target +// */ +// TransferTarget transferMe = createTransferTarget(targetName); +// +// /** +// * Create a test node that we will read and write +// */ +// ChildAssociationRef child = nodeService.createNode(guestHome, ContentModel.ASSOC_CONTAINS, QName.createQName("testNode6"), ContentModel.TYPE_CONTENT); +// +// +// File tempFile = TempFileProvider.createTempFile("test", ".dat"); +// FileWriter fw = new FileWriter(tempFile); +// for(int i = 0; i < 100000000; i++) +// { +// fw.write("hello world this is my text, I wonder how much text I can transfer?" + i); +// } +// System.out.println("Temp File Size is:" + tempFile.length()); +// fw.close(); +// +// NodeRef contentNodeRef = child.getChildRef(); +// ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, true); +// writer.setLocale(CONTENT_LOCALE); +// //File file = new File("c:/temp/images/BigCheese1.bmp"); +// writer.setMimetype("application/data"); +// //writer.putContent(file); +// writer.putContent(tempFile); +// +// tempFile.delete(); +// +// nodeService.setProperty(contentNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); +// nodeService.setProperty(contentNodeRef, ContentModel.PROP_NAME, CONTENT_NAME); +// +// try +// { +// /** +// * Transfer the node created above +// */ +// { +// TransferDefinition definition = new TransferDefinition(); +// Setnodes = new HashSet(); +// nodes.add(contentNodeRef); +// definition.setNodes(nodes); +// transferService.transfer(targetName, definition, null); +// } +// +// /** +// * Negative test transfer nothing +// */ +// try +// { +// TransferDefinition definition = new TransferDefinition(); +// transferService.transfer(targetName, definition, null); +// fail("exception not thrown"); +// } +// catch(TransferException te) +// { +// // expect to go here +// } +// } +// finally +// { +// transferService.deleteTransferTarget(targetName); +// } +// } + + + private TransferTarget createTransferTarget(String name) + { + String title = "title"; + String description = "description"; + String endpointProtocol = "http"; + String endpointHost = "MARKR02"; + int endpointPort = 6080; + String endpointPath = "/alfresco/service/api/transfer"; + String username = "admin"; + char[] password = "admin".toCharArray(); + + /** + * Now go ahead and create our first transfer target + */ + TransferTarget target = transferService.createTransferTarget(name, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + return target; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/TransferTargetImpl.java b/source/java/org/alfresco/repo/transfer/TransferTargetImpl.java new file mode 100644 index 0000000000..369266cc0a --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferTargetImpl.java @@ -0,0 +1,174 @@ +package org.alfresco.repo.transfer; +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.transfer.TransferTarget; + +/** + * Data Transfer Object for a TransferTarget. The definition of the connection to a remote system. + * + * @author Mark Rogers + */ +public class TransferTargetImpl implements TransferTarget +{ + private NodeRef nodeRef; + private String name; + private String title; + private String description; + private String endpointProtocol; + private String endpointHost; + private int endpointPort; + private String endpointPath; + private String username; + private char[] password; + private boolean enabled; + + public void setNodeRef(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + public NodeRef getNodeRef() + { + return nodeRef; + } + public void setName(String name) + { + this.name = name; + } + public String getName() + { + return name; + } + public void setTitle(String title) + { + this.title = title; + } + public String getTitle() + { + return title; + } + public void setDescription(String description) + { + this.description = description; + } + public String getDescription() + { + return description; + } + public void setEndpointProtocol(String endpointProtocol) + { + this.endpointProtocol = endpointProtocol; + } + public String getEndpointProtocol() + { + return endpointProtocol; + } + public void setEndpointHost(String endpointHost) + { + this.endpointHost = endpointHost; + } + public String getEndpointHost() + { + return endpointHost; + } + public void setPassword(char[] password) + { + this.password = password; + } + public char[] getPassword() + { + return password; + } + public void setUsername(String username) + { + this.username = username; + } + public String getUsername() + { + return username; + } + public void setEndpointPath(String endpointPath) + { + this.endpointPath = endpointPath; + } + public String getEndpointPath() + { + return endpointPath; + } + public void setEndpointPort(int endpointPort) + { + this.endpointPort = endpointPort; + } + public int getEndpointPort() + { + return endpointPort; + } + + /** + * @see #getNodeRef() + * @see NodeRef#equals(Object) + */ + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + else if (this == obj) + { + return true; + } + else if (obj instanceof TransferTargetImpl == false) + { + return false; + } + TransferTargetImpl that = (TransferTargetImpl) obj; + return (this.getNodeRef().equals(that.getNodeRef())); + } + + /** + * @see #getNodeRef() + * @see NodeRef#hashCode() + */ + public int hashCode() + { + return getNodeRef().hashCode(); + } + + public void setEnabled(boolean enabled) + { + this.enabled = enabled; + } + + public boolean isEnabled() + { + return enabled; + } + + public String toString() + { + return "TransferTarget: " + name + ",host:" + endpointHost + ",port:" + endpointPort; + } +} diff --git a/source/java/org/alfresco/repo/transfer/TransferTransmitter.java b/source/java/org/alfresco/repo/transfer/TransferTransmitter.java new file mode 100644 index 0000000000..e03c802330 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/TransferTransmitter.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.transfer; + +import java.io.File; +import java.io.InputStream; +import java.util.Set; + +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.cmr.transfer.TransferTarget; + +/** + * @author brian + * + */ +public interface TransferTransmitter +{ + /** + * Verify that the target is available + * @param target + * @throws TransferException + */ + void verifyTarget(TransferTarget target) throws TransferException; + + /** + * Begin a transfer, the transfer object returned will be used by subsequent + * calls to the transfer service. + * + * @param target definition of where to transfer to. + * @return the transfer object or null if the target cannot be locked. + * @throws TransferException + */ + Transfer begin(TransferTarget target) throws TransferException; + + /** + * @param manifest, the transfer manifest file + * @param transfer the transfer object returned by an earlier call to begin + * @return the delta list. + * @throws TransferException + */ + DeltaList sendManifest(Transfer transfer, File manifest) throws TransferException; + + /** + * Send the content of the specified urls + * + * @param transfer the transfer object returned by an earlier call to begin + * @param data the content to send + * @throws TransferException + */ + void sendContent(Transfer transfer, Set data); + + /** + * + * @param transfer the transfer object returned by an earlier call to begin + * @throws TransferException + */ + void prepare(Transfer transfer) throws TransferException; + + /** + * @param transfer the transfer object returned by an earlier call to begin + * @throws TransferException + */ + void commit(Transfer transfer) throws TransferException; + + /** + * Abort the transfer + * @param transfer the transfer object returned by an earlier call to begin + * @throws TransferException + */ + void abort(Transfer transfer) throws TransferException; + + /** + * Get Async Messages for a transfer. + * Server Side Messages. + * @return messages + */ + Set getMessages(Transfer transfer); +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/transfer/UnitTestInProcessTransmitterImpl.java b/source/java/org/alfresco/repo/transfer/UnitTestInProcessTransmitterImpl.java new file mode 100644 index 0000000000..0a3f948c83 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/UnitTestInProcessTransmitterImpl.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Set; + +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.cmr.transfer.TransferReceiver; +import org.alfresco.service.cmr.transfer.TransferTarget; + +/** + * This class delegates transfer service to the transfer receiver without + * using any networking. + * + * It is used for unit testing the transfer service without requiring two instance + * of the repository to be running. + * + * @author Mark Rogers + */ +public class UnitTestInProcessTransmitterImpl implements TransferTransmitter +{ + private TransferReceiver receiver; + + private ContentService contentService; + + public UnitTestInProcessTransmitterImpl(TransferReceiver receiver, ContentService contentService) + { + this.receiver = receiver; + this.contentService = contentService; + } + + public Transfer begin(TransferTarget target) throws TransferException + { + Transfer transfer = new Transfer(); + String transferId = receiver.start(); + transfer.setTransferId(transferId); + transfer.setTransferTarget(target); + return transfer; + } + + public void abort(Transfer transfer) throws TransferException + { + String transferId = transfer.getTransferId(); + receiver.abort(transferId); + } + + public void commit(Transfer transfer) throws TransferException + { + String transferId = transfer.getTransferId(); + receiver.commit(transferId); + } + + public Set getMessages(Transfer transfer) + { + String transferId = transfer.getTransferId(); + return null; + } + + public void prepare(Transfer transfer) throws TransferException + { + String transferId = transfer.getTransferId(); + receiver.prepare(transferId); + } + + public void sendContent(Transfer transfer, Set data) + { + String transferId = transfer.getTransferId(); + + for(ContentData content : data) + { + String contentUrl = content.getContentUrl(); + String fileName = TransferCommons.URLToPartName(contentUrl); + + InputStream contentStream = getContentService().getRawReader(contentUrl).getContentInputStream(); + receiver.saveContent(transferId, fileName, contentStream); + } + } + + public DeltaList sendManifest(Transfer transfer, File manifest) throws TransferException + { + try + { + String transferId = transfer.getTransferId(); + FileInputStream fs = new FileInputStream(manifest); + receiver.saveSnapshot(transferId, fs); + } + catch (FileNotFoundException error) + { + throw new TransferException("test error", error); + } + return null; + + } + + public void verifyTarget(TransferTarget target) throws TransferException + { + + } + + public void setReceiver(TransferReceiver receiver) + { + this.receiver = receiver; + } + + public TransferReceiver getReceiver() + { + return receiver; + } + + private void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + private ContentService getContentService() + { + return contentService; + } +} diff --git a/source/java/org/alfresco/repo/transfer/UnitTestTransferManifestNodeFactory.java b/source/java/org/alfresco/repo/transfer/UnitTestTransferManifestNodeFactory.java new file mode 100644 index 0000000000..a2d86d514c --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/UnitTestTransferManifestNodeFactory.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.transfer.manifest.TransferManifestNode; +import org.alfresco.repo.transfer.manifest.TransferManifestNodeFactory; +import org.alfresco.repo.transfer.manifest.TransferManifestNormalNode; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; + +/** + * This is a test class to enable unit testing on a single machine. Since the single machine will already have the + * target node. + * + * @author Mark Rogers + */ +public class UnitTestTransferManifestNodeFactory implements TransferManifestNodeFactory +{ + /** + * Map of source to target noderef. + */ + Map refMap = new HashMap(); + + /** + * List of paths + * + * + */ + private List> pathMap = new ArrayList>(); + + /** + * The node factory that does the work for real. + */ + TransferManifestNodeFactory realFactory; + + /** + * Create a new UnitTestTransferManifestNodeFactory + * + * @param realFactory + */ + UnitTestTransferManifestNodeFactory(TransferManifestNodeFactory realFactory) + { + this.realFactory = realFactory; + } + + public TransferManifestNode createTransferManifestNode(NodeRef nodeRef) + { + TransferManifestNode newNode = realFactory.createTransferManifestNode(nodeRef); + + NodeRef origNodeRef = newNode.getNodeRef(); + + /** + * Fiddle with the node ref to prevent a clash with the source + */ + NodeRef mappedNodeRef = mapNodeRef(origNodeRef); + newNode.setNodeRef(mappedNodeRef); + + /** + * Fiddle with the parent node ref and parent path. + */ + ChildAssociationRef primaryParentAssoc = newNode.getPrimaryParentAssoc(); + NodeRef mappedParentNodeRef = mapNodeRef(primaryParentAssoc.getParentRef()); + Path parentPath = newNode.getParentPath(); + newNode.setParentPath(getMappedPath(parentPath)); + newNode.setPrimaryParentAssoc(new ChildAssociationRef(primaryParentAssoc.getTypeQName(), mappedParentNodeRef, + primaryParentAssoc.getQName(), mappedNodeRef, primaryParentAssoc.isPrimary(), + primaryParentAssoc.getNthSibling())); + + /** + * Fiddle with the parent assocs + */ + if (newNode instanceof TransferManifestNormalNode) + { + TransferManifestNormalNode normalNode = (TransferManifestNormalNode) newNode; + List mappedParentAssocs = new ArrayList(); + List assocs = normalNode.getParentAssocs(); + for (ChildAssociationRef assoc : assocs) + { + ChildAssociationRef replace = new ChildAssociationRef(assoc.getTypeQName(), mappedParentNodeRef, + assoc.getQName(), mappedNodeRef, assoc.isPrimary(), assoc.getNthSibling()); + mappedParentAssocs.add(replace); + } + normalNode.setParentAssocs(mappedParentAssocs); + } + + /** + * Fiddle with the UUID property + */ + if (newNode instanceof TransferManifestNormalNode) + { + TransferManifestNormalNode normalNode = (TransferManifestNormalNode) newNode; + Map props = normalNode.getProperties(); + + if (props.containsKey(ContentModel.PROP_NODE_UUID)) + { + props.put(ContentModel.PROP_NODE_UUID, mappedNodeRef.getId()); + } + } + + return newNode; + } + + /** + * Get the mapped node ref + * + * @param node + * @return the mapped node ref or null; + */ + public NodeRef getMappedNodeRef(NodeRef node) + { + return refMap.get(node); + } + + /** + * Get mapped path + */ + public Path getMappedPath(Path from) + { + Path to = new Path(); + + /** + * + */ + Path source = new Path(); + for (int i = 0; i < from.size(); i++) + { + // Source steps through each element of from. + source.append(from.get(i)); + boolean replacePath = false; + for (Pair xx : getPathMap()) + { + // Can't use direct equals because of mismatched node refs (which we don't care about) + if (xx.getFirst().toString().equals(source.toString())) + { + to = xx.getSecond().subPath(xx.getSecond().size() - 1); + replacePath = true; + break; + } + } + if (!replacePath) + { + to.append(from.get(i)); + } + } + + return to; + } + + private NodeRef mapNodeRef(NodeRef in) + { + NodeRef mappedNodeRef = refMap.get(in); + if (mappedNodeRef == null) + { + /** + * Map the node ref by replacing the 36th digit with a Z. The existing UUID could have 0-9 1-F in the 36th + * digit + */ + String nodeRef = in.getId(); + if (nodeRef.length() == 36) + { + nodeRef = in.getId().substring(0, 35) + "Z"; + + } + else + { + nodeRef = in.getId() + "Z"; + } + + mappedNodeRef = new NodeRef(in.getStoreRef(), nodeRef); + refMap.put(in, mappedNodeRef); + } + return mappedNodeRef; + } + + public void setPathMap(List> pathMap) + { + this.pathMap = pathMap; + } + + public List> getPathMap() + { + return pathMap; + } +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/ManifestIntegrationTest.java b/source/java/org/alfresco/repo/transfer/manifest/ManifestIntegrationTest.java new file mode 100644 index 0000000000..3c14de96a5 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/ManifestIntegrationTest.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.Writer; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.transfer.manifest.TransferManifestHeader; +import org.alfresco.repo.transfer.manifest.TransferManifestNode; +import org.alfresco.repo.transfer.manifest.TransferManifestNodeFactoryImpl; +import org.alfresco.repo.transfer.manifest.TransferManifestWriter; +import org.alfresco.repo.transfer.manifest.XMLTransferManifestWriter; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.MLText; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.cmr.transfer.TransferService; +import org.alfresco.service.cmr.transfer.TransferTarget; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseAlfrescoSpringTest; +import org.alfresco.util.TempFileProvider; + +/** + * Integration test for Transfer Manifest + * + * @author Mark Rogers + */ +public class ManifestIntegrationTest extends BaseAlfrescoSpringTest +{ + private TransferService transferService; + private ContentService contentService; + private SearchService searchService; + + /** + * Called during the transaction setup + */ + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + + // Get the required services + this.transferService = (TransferService)this.applicationContext.getBean("TransferService"); + this.contentService = (ContentService)this.applicationContext.getBean("ContentService"); + this.searchService = (SearchService)this.applicationContext.getBean("SearchService"); + } + + public void testSnapshot() throws Exception + { + // Snapshot a transfer node + String CONTENT_STRING = "hello world"; + Locale CONTENT_LOCALE = Locale.TAIWAN; + String CONTENT_TITLE = "the title"; + String CONTENT_NAME = "&the name <\\*"; // nasty name for XML + String CONTENT_ASSOC_NAME = "&hell+-1we"; + + String snapshotMe = "snapshotMe"; + String title = "title"; + String description = "description"; + String endpointProtocol = "http"; + String endpointHost = "localhost"; + int endpointPort = 8080; + String endpointPath = "rhubarb"; + String username = "admin"; + char[] password = "password".toCharArray(); + + Map sentNodes = new HashMap(); + + TransferManifestNodeFactoryImpl nodeFactory = new TransferManifestNodeFactoryImpl(); + nodeFactory.setNodeService(nodeService); + + /** + * Create our transfer target + */ + TransferTarget target = transferService.createTransferTarget(snapshotMe, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password); + + File snapshotFile = null; + + try + { + /** + * Create a test node that we will read and write + */ + ChildAssociationRef child = nodeService.createNode(target.getNodeRef(), ContentModel.ASSOC_CONTAINS, QName.createQName(CONTENT_ASSOC_NAME), ContentModel.TYPE_CONTENT); + + NodeRef childNodeRef = child.getChildRef(); + ContentWriter writer = contentService.getWriter(childNodeRef, ContentModel.PROP_CONTENT, true); + writer.setLocale(CONTENT_LOCALE); + writer.putContent(CONTENT_STRING); + + nodeService.setProperty(childNodeRef, ContentModel.PROP_TITLE, CONTENT_TITLE); + + nodeService.setProperty(childNodeRef, ContentModel.PROP_NAME, CONTENT_NAME); + + snapshotFile = TempFileProvider.createTempFile("xxx", ".xml"); + FileWriter snapshotWriter = new FileWriter(snapshotFile); + + Set nodes = new HashSet(); + + nodes.add(nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE)); + nodes.add(target.getNodeRef()); + nodes.add(childNodeRef); + + TransferManifestWriter formatter = new XMLTransferManifestWriter(); + TransferManifestHeader header = new TransferManifestHeader(); + header.setCreatedDate(new Date()); + formatter.startTransferManifest(snapshotWriter); + formatter.writeTransferManifestHeader(header); + for(NodeRef nodeRef : nodes) + { + TransferManifestNode node = nodeFactory.createTransferManifestNode(nodeRef); + formatter.writeTransferManifestNode(node); + sentNodes.put(nodeRef, node); + } + formatter.endTransferManifest(); + snapshotWriter.close(); + + // Show the snapshot file (For dev purposes) + outputFile(snapshotFile); + + /** + * Now read the snapshot file + */ + TestTransferManifestProcessor processor = new TestTransferManifestProcessor(); + XMLTransferManifestReader reader = new XMLTransferManifestReader(processor); + + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + SAXParser parser = saxParserFactory.newSAXParser(); + parser.parse(snapshotFile, reader); + + /** + * Now validate that we read back what we write out + */ + assertEquals("did not get back the same number of nodes", nodes.size(), processor.getNodes().size()); + assertNotNull("header is null", processor.getHeader()); + + for(NodeRef nodeId : nodes) + { + System.out.println("Processing node:" + nodeId); + TransferManifestNormalNode readNode = (TransferManifestNormalNode)processor.getNodes().get(nodeId); + TransferManifestNormalNode writeNode = (TransferManifestNormalNode)sentNodes.get(nodeId); + assertNotNull("readNode is null", readNode); + assertNotNull("writeNode is null", writeNode); + + assertEquals("type is different", writeNode.getType(), readNode.getType()); + assertEquals("nodeRef is different", writeNode.getNodeRef(), readNode.getNodeRef()); + assertEquals("parent node ref is different", writeNode.getPrimaryParentAssoc(), readNode.getPrimaryParentAssoc()); + if(writeNode.getParentPath() != null) + { + assertEquals("parent path is different", writeNode.getParentPath().toString(), readNode.getParentPath().toString()); + } + + assertEquals("aspects array different size", writeNode.getAspects().size(), readNode.getAspects().size()); + for(QName aspect : writeNode.getAspects()) + { + assertTrue("missing aspect", readNode.getAspects().contains(aspect)); + } + + assertEquals("properties array different size", writeNode.getProperties().size(), readNode.getProperties().size()); + for(QName prop : writeNode.getProperties().keySet()) + { + assertTrue("missing property", readNode.getProperties().containsKey(prop)); + } + + assertEquals("child assocs different", writeNode.getChildAssocs().size(), readNode.getChildAssocs().size()); + assertEquals("parent assocs different", writeNode.getParentAssocs().size(), readNode.getParentAssocs().size()); + assertEquals("source assocs different", writeNode.getSourceAssocs().size(), readNode.getSourceAssocs().size()); + assertEquals("target assocs different", writeNode.getTargetAssocs().size(), readNode.getTargetAssocs().size()); + + if(readNode.getNodeRef().equals(childNodeRef)) + { + /** + * Check the child node since we created it at the start of this test this test + */ + ContentData data = (ContentData)readNode.getProperties().get(ContentModel.PROP_CONTENT); + assertEquals("content data wrong size", data.getSize(), CONTENT_STRING.length()); + assertEquals("content locale wrong", data.getLocale(), CONTENT_LOCALE); + + String childTitle = (String)readNode.getProperties().get(ContentModel.PROP_TITLE); + assertEquals("content title wrong", childTitle, CONTENT_TITLE); + + String childName = (String)readNode.getProperties().get(ContentModel.PROP_NAME); + assertEquals("content name wrong", childName, CONTENT_NAME); + + /** + * Check the parent associations, there should be only one primary + */ + assertTrue("one parent assoc", readNode.getParentAssocs().size() == 1); + assertTrue("isPrimary", readNode.getParentAssocs().get(0).isPrimary()); + assertEquals("parent q name", readNode.getParentAssocs().get(0).getQName(), QName.createQName(CONTENT_ASSOC_NAME)); + assertEquals("parent type q name", readNode.getParentAssocs().get(0).getTypeQName(), ContentModel.ASSOC_CONTAINS); + assertEquals("child node ref", readNode.getParentAssocs().get(0).getChildRef(), childNodeRef); + assertEquals("parent node ref", readNode.getParentAssocs().get(0).getParentRef(), readNode.getPrimaryParentAssoc()); + assertTrue("zero child assoc", readNode.getChildAssocs().size() == 0); + + /** + * Test Node Helper + */ + assertEquals(readNode.getParentAssocs().get(0), TransferManifestNodeHelper.getPrimaryParentAssoc(readNode)); + + Set content = TransferManifestNodeHelper.getContentData(readNode); + assertEquals("content not found", content.size(), 1); + } + } + + + } + finally + { + if(snapshotFile != null) + { + snapshotFile.delete(); + } + transferService.deleteTransferTarget(snapshotMe); + } + } + + /** + * Utility to dump the contents of a file to the console + * @param file + */ + private static void outputFile(File file) throws Exception + { + BufferedReader reader = new BufferedReader(new FileReader(file)); + String s = reader.readLine(); + while(s != null) + { + System.out.println(s); + s = reader.readLine(); + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/transfer/manifest/ManifestModel.java b/source/java/org/alfresco/repo/transfer/manifest/ManifestModel.java new file mode 100644 index 0000000000..f11c5e950a --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/ManifestModel.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import org.alfresco.repo.transfer.TransferModel; +import org.alfresco.service.namespace.QName; + +/** + * The transfer model - extended for XML Manifest Model + */ +public interface ManifestModel extends TransferModel +{ + static final String LOCALNAME_TRANSFER_MAINIFEST = "transferManifest"; + static final String LOCALNAME_TRANSFER_HEADER = "transferManifestHeader"; + static final String LOCALNAME_HEADER_CREATED_DATE = "createdDate"; + static final String LOCALNAME_ELEMENT_NODES = "nodes"; + static final String LOCALNAME_ELEMENT_NODE = "node"; + static final String LOCALNAME_ELEMENT_DELETED_NODE = "deletedNode"; + static final String LOCALNAME_ELEMENT_ASPECTS = "aspects"; + static final String LOCALNAME_ELEMENT_ASPECT = "aspect"; + static final String LOCALNAME_ELEMENT_PROPERTIES = "properties"; + static final String LOCALNAME_ELEMENT_PROPERTY = "property"; + static final String LOCALNAME_ELEMENT_PARENT_ASSOCS = "parentAssocs"; + static final String LOCALNAME_ELEMENT_CHILD_ASSOCS = "childAssocs"; + static final String LOCALNAME_ELEMENT_CHILD_ASSOC = "childAssoc"; + static final String LOCALNAME_ELEMENT_PARENT_ASSOC = "parentAssoc"; + static final String LOCALNAME_ELEMENT_TARGET_ASSOCS = "targetAssocs"; + static final String LOCALNAME_ELEMENT_SOURCE_ASSOCS = "sourceAssocs"; + static final String LOCALNAME_ELEMENT_ASSOC = "assoc"; + static final String LOCALNAME_ELEMENT_PRIMARY_PARENT = "primaryParent"; + static final String LOCALNAME_ELEMENT_PRIMARY_PATH = "primaryPath"; + static final String LOCALNAME_ELEMENT_VALUES = "values"; + static final String LOCALNAME_ELEMENT_VALUE = "value"; + static final String LOCALNAME_ELEMENT_MLVALUE = "mlvalue"; + static final String LOCALNAME_ELEMENT_CONTENT_HEADER = "content"; + + // Manifest file prefix + static final String MANIFEST_PREFIX = "xfer"; +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/TestTransferManifestProcessor.java b/source/java/org/alfresco/repo/transfer/manifest/TestTransferManifestProcessor.java new file mode 100644 index 0000000000..63c9be8e1e --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/TestTransferManifestProcessor.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Test implementation of TransferManifestProcessor. + * + * Simply gives access to the data through header and nodes properties. + * + * @author Mark Rogers + */ +public class TestTransferManifestProcessor implements TransferManifestProcessor +{ + private TransferManifestHeader header; + private Map nodes = new HashMap(); + + public void endTransferManifest() + { + } + + public void processTransferManifestNode(TransferManifestNormalNode node) + { + nodes.put(node.getNodeRef(), node); + } + + public void processTransferManifestNode(TransferManifestDeletedNode node) + { + nodes.put(node.getNodeRef(), node); + } + + public void processTransferManifiestHeader(TransferManifestHeader header) + { + this.header = header; + } + + public void startTransferManifest() + { + } + + void setHeader(TransferManifestHeader header) + { + this.header = header; + } + + TransferManifestHeader getHeader() + { + return header; + } + + void setNodes( Map nodes) + { + this.nodes = nodes; + } + + Map getNodes() + { + return nodes; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestDeletedNode.java b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestDeletedNode.java new file mode 100644 index 0000000000..18b3863b98 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestDeletedNode.java @@ -0,0 +1,62 @@ +package org.alfresco.repo.transfer.manifest; + +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; + +/** + * A record of a deleted node in the transfer manifest + * + * The path and node ref refers to the state prior to the node's deletion. + * + * @author Mark Rogers + */ +public class TransferManifestDeletedNode implements TransferManifestNode +{ + private NodeRef nodeRef; + private ChildAssociationRef primaryParentAssoc; + private String uuid; + private Path parentPath; + + public void setNodeRef(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + + public NodeRef getNodeRef() + { + return nodeRef; + } + + public void setUuid(String uuid) + { + this.uuid = uuid; + } + + public String getUuid() + { + return uuid; + } + + public void setParentPath(Path parentPath) + { + this.parentPath = parentPath; + } + + public Path getParentPath() + { + return parentPath; + } + + public void setPrimaryParentAssoc(ChildAssociationRef parentAssoc) + { + this.primaryParentAssoc = parentAssoc; + } + + public ChildAssociationRef getPrimaryParentAssoc() + { + return primaryParentAssoc; + } + + +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestHeader.java b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestHeader.java new file mode 100644 index 0000000000..ae808a2246 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestHeader.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import java.util.Date; + +/** + * Data value object + * + * Part of the transfer manifest + */ + +public class TransferManifestHeader +{ + private Date createdDate; + + public void setCreatedDate(Date createDate) + { + this.createdDate = createDate; + } + + public Date getCreatedDate() + { + return createdDate; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNode.java b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNode.java new file mode 100644 index 0000000000..518cc4d5f2 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNode.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; + +/** + * Data value object - part of the transfer manifest + * + * Represents a single node in the transfer manifest + * + * @see org.alfresco.repo.transfer.manifest.TransferManifestDeletedNode + * @see org.alfresco.repo.transfer.manifest.TransferManifestNormalNode + * + * @author Mark Rogers + */ +public interface TransferManifestNode +{ + public NodeRef getNodeRef(); + public void setNodeRef(NodeRef nodeRef); + + public void setUuid(String uuid); + public String getUuid(); + + public void setParentPath(Path parentPath); + public Path getParentPath(); + + public void setPrimaryParentAssoc(ChildAssociationRef primaryParent); + public ChildAssociationRef getPrimaryParentAssoc(); +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeFactory.java b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeFactory.java new file mode 100644 index 0000000000..4d1ad97705 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import org.alfresco.service.cmr.repository.NodeRef; + +public interface TransferManifestNodeFactory +{ + TransferManifestNode createTransferManifestNode(NodeRef nodeRef); +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeFactoryImpl.java b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeFactoryImpl.java new file mode 100644 index 0000000000..6559df7069 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeFactoryImpl.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.transfer.TransferException; +import org.alfresco.service.namespace.RegexQNamePattern; + +public class TransferManifestNodeFactoryImpl implements TransferManifestNodeFactory +{ + private NodeService nodeService; + + public void init() + { + + } + + public TransferManifestNode createTransferManifestNode(NodeRef nodeRef) + { + NodeRef.Status status = nodeService.getNodeStatus(nodeRef); + + if(status == null) + { + throw new TransferException("Unable to get node status for node : " + nodeRef); + } + + /** + * Work out whether this is a deleted node or not + */ + if(nodeRef.getStoreRef().equals(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE) || status.isDeleted()) + { + if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_ARCHIVED)) + { + // Yes we have an archived aspect + ChildAssociationRef car = (ChildAssociationRef)nodeService.getProperty(nodeRef, + ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); + + TransferManifestDeletedNode node = new TransferManifestDeletedNode(); + NodeRef parentNodeRef = car.getParentRef(); + node.setNodeRef(car.getChildRef()); + node.setPrimaryParentAssoc(car); + + if(nodeService.exists(parentNodeRef)) + { + // The parent node still exists so it still has a path. + Path parentPath = nodeService.getPath(parentNodeRef); + node.setParentPath(parentPath); + } + + return node; + } + + // No we don't have an archived aspect - maybe we are not yet committed + TransferManifestDeletedNode node = new TransferManifestDeletedNode(); + node.setNodeRef(nodeRef); + ChildAssociationRef parentAssocRef = nodeService.getPrimaryParent(nodeRef); + if(parentAssocRef != null && parentAssocRef.getParentRef() != null) + { + NodeRef parentNodeRef = parentAssocRef.getParentRef(); + node.setPrimaryParentAssoc(parentAssocRef); + Path parentPath = nodeService.getPath(parentNodeRef); + node.setParentPath(parentPath); + } + + return node; + } + else + { + // This is a "normal" node + + TransferManifestNormalNode node = new TransferManifestNormalNode(); + node.setNodeRef(nodeRef); + node.setProperties(nodeService.getProperties(nodeRef)); + node.setAspects(nodeService.getAspects(nodeRef)); + node.setType(nodeService.getType(nodeRef)); + ChildAssociationRef parentAssocRef = nodeService.getPrimaryParent(nodeRef); + if(parentAssocRef != null && parentAssocRef.getParentRef() != null) + { + NodeRef parentNodeRef = parentAssocRef.getParentRef(); + node.setPrimaryParentAssoc(parentAssocRef); + Path parentPath = nodeService.getPath(parentNodeRef); + node.setParentPath(parentPath); + } + node.setChildAssocs(nodeService.getChildAssocs(nodeRef)); + node.setParentAssocs(nodeService.getParentAssocs(nodeRef)); + node.setTargetAssocs(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL )); + node.setSourceAssocs(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); + + return node; + } + } + + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public NodeService getNodeService() + { + return nodeService; + } +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeHelper.java b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeHelper.java new file mode 100644 index 0000000000..36cee5dc97 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNodeHelper.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; + +import java.io.Serializable; + +/** + * Decorator to extend capabilities of TransferManifestNode + * + * @author Mark Rogers + */ +public class TransferManifestNodeHelper +{ + /** + * Gets the primary parent association + * @param node the node to process + * @return the primary parent association or null if this is a root node + */ + public static ChildAssociationRef getPrimaryParentAssoc(TransferManifestNormalNode node) + { + List assocs = node.getParentAssocs(); + + for(ChildAssociationRef assoc : assocs) + { + if(assoc.isPrimary()) + { + return assoc; + } + } + return null; + } + + /** + * Gets the content properties for a node + * @param node the node to process + * @return + */ + public static Set getContentData(TransferManifestNormalNode node) + { + Set content = new HashSet(); + + for(Serializable value : node.getProperties().values()) + { + if(value instanceof ContentData) + { + content.add((ContentData)value); + } + } + + return content; + } +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNormalNode.java b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNormalNode.java new file mode 100644 index 0000000000..efafb86e04 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestNormalNode.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.namespace.QName; + +/** + * Data value object - part of the transfer manifest + * + * Represents a single node and either a create or an update. + * + * @author Mark Rogers + */ +public class TransferManifestNormalNode implements TransferManifestNode +{ + private NodeRef nodeRef; + private ChildAssociationRef primaryParentAssoc; + private String uuid; + private QName type; + private Map properties; + private Set aspects; + private List childAssocs; + private List parentAssocs; + private List sourceAssocs; + private List targetAssocs; + private Path parentPath; + + public void setNodeRef(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + + public NodeRef getNodeRef() + { + return nodeRef; + } + + public void setUuid(String uuid) + { + this.uuid = uuid; + } + + public String getUuid() + { + return uuid; + } + + /** + * Gets all properties for the node + * + * @return the properties + */ + public Map getProperties() + { + return properties; + } +// +// /** +// * Gets the property data type +// * +// * @param propertyName name of property +// * @return data type of named property +// */ +// public DataTypeDefinition getPropertyDataType(QName propertyName); +// +// /** +// * @return the aspects of this node +// */ +// public Set getNodeAspects(); +// +// /** +// * @return true => the node inherits permissions from its parent +// */ +// public boolean getInheritPermissions(); +// +// /** +// * @return the permissions applied to this node +// */ +// public List getAccessControlEntries(); + + public void setProperties(Map properties) + { + this.properties = properties; + } + + public void setAspects(Set aspects) + { + this.aspects = aspects; + } + + public Set getAspects() + { + return aspects; + } + + public void setType(QName type) + { + this.type = type; + } + + public QName getType() + { + return type; + } + + public void setChildAssocs(List childAssocs) + { + this.childAssocs = childAssocs; + } + + public List getChildAssocs() + { + return childAssocs; + } + + public void setParentAssocs(List parentAssocs) + { + this.parentAssocs = parentAssocs; + } + + public List getParentAssocs() + { + return parentAssocs; + } + + public void setParentPath(Path parentPath) + { + this.parentPath = parentPath; + } + + public Path getParentPath() + { + return parentPath; + } + + public void setSourceAssocs(List sourceAssocs) + { + this.sourceAssocs = sourceAssocs; + } + + public List getSourceAssocs() + { + return sourceAssocs; + } + + public void setTargetAssocs(List targetAssocs) + { + this.targetAssocs = targetAssocs; + } + + public List getTargetAssocs() + { + return targetAssocs; + } + + public void setPrimaryParentAssoc(ChildAssociationRef primaryParentAssoc) + { + this.primaryParentAssoc = primaryParentAssoc; + } + + public ChildAssociationRef getPrimaryParentAssoc() + { + return primaryParentAssoc; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestProcessor.java b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestProcessor.java new file mode 100644 index 0000000000..f32d601a03 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestProcessor.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +/** + * Manifest Processor + * + * Interface called when parsing the transfer manifest file + * + * When Parsing the manifest file, the startTransferManifest will be called first, then + * processHeader, then mulpiple calls of processTransferManifestNode, one for each node, + * then endTransferManifest + * + * @author Mark Rogers + */ +public interface TransferManifestProcessor +{ + /** + * Signals the start of a transfer manifest + */ + public void startTransferManifest(); + + /** + * Gives the header to be proceessed + * @param header, the header + */ + public void processTransferManifiestHeader(TransferManifestHeader header); + + /** + * Gives a manifest node to be processed + * @param node, the node + */ + public void processTransferManifestNode(TransferManifestNormalNode node); + + /** + * Gives a deleted manifest node to be processed + * @param node, the node + */ + public void processTransferManifestNode(TransferManifestDeletedNode node); + + /** + * Signals the end of a transfer manifest + */ + public void endTransferManifest(); + +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestTest.java b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestTest.java new file mode 100644 index 0000000000..27e1f49565 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestTest.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.Reader; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.MLText; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.TempFileProvider; + +import junit.framework.TestCase; + +public class TransferManifestTest extends TestCase +{ + public void testCreateAndReadSnapshot() throws Exception + { + /** + * create snapshot + */ + String prefix = "TRX-SNAP"; + String suffix = ".xml"; + + // where to put snapshot ? + File snapshotFile = TempFileProvider.createTempFile(prefix, suffix); + FileWriter snapshotWriter = new FileWriter(snapshotFile); + + // Write the manifest file + TransferManifestWriter formatter = new XMLTransferManifestWriter(); + TransferManifestHeader header = new TransferManifestHeader(); + header.setCreatedDate(new Date()); + formatter.startTransferManifest(snapshotWriter); + formatter.writeTransferManifestHeader(header); + + // node to transmit + TransferManifestNormalNode node = new TransferManifestNormalNode(); + node.setNodeRef(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "123")); + node.setParentPath(new Path()); + Set aspects = new HashSet(); + aspects.add(QName.createQName("{gsxhjsx}", "cm:wobble")); + aspects.add(QName.createQName("{gsxhjsx}", "cm:wibble")); + node.setAspects(aspects); + + Map properties = new HashMap(); + properties.put(QName.createQName("{gsxhjsx}", "cm:name"), "brian.jpg"); + properties.put(QName.createQName("{gsxhjsx}", "cm:created"), new java.util.Date()); + properties.put(QName.createQName("{gsxhjsx}", "trx:enabled"), Boolean.FALSE); + MLText mltext = new MLText(); + mltext.addValue(Locale.FRENCH, "Bonjour"); + mltext.addValue(Locale.ENGLISH, "Hello"); + mltext.addValue(Locale.ITALY, "Buongiorno"); + properties.put(QName.createQName("{gsxhjsx}", "cm:title"), mltext); + String password = "helloWorld"; + properties.put(QName.createQName("{gsxhjsx}", "trx:password"), password.toCharArray()); + + + List parents = new ArrayList(); + ChildAssociationRef primaryParent = new ChildAssociationRef(QName.createQName("{gsxhjsx}", "cm:contains"), + new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "P1"), + QName.createQName("{gsxhjsx}", "app:smashing"), + new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "123"), + true, + -1); + parents.add(primaryParent); + parents.add(new ChildAssociationRef(QName.createQName("{gsxhjsx}", "app:wibble"), + new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "P1"), + QName.createQName("{gsxhjsx}", "app:jskjsdc"), + new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "123"), + false, + -1)); + node.setParentAssocs(parents); + node.setPrimaryParentAssoc(primaryParent); + + List children = new ArrayList(); + children.add(new ChildAssociationRef(QName.createQName("{gsxhjsx}", "cm:contains"), + new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "P1"), + QName.createQName("{gsxhjsx}", "app:super"), + new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "P5"), + true, + -1)); + + node.setChildAssocs(children); + + Setvalues=new HashSet(); + values.add("red"); + values.add("blue"); + values.add("green"); + properties.put(QName.createQName("{gsxhjsx}", "xyz:colours"), (Serializable)values); + + ContentData contentHeader = new ContentData("http://wibble", "mimeType", 123, "utf-8", Locale.ENGLISH); + properties.put(QName.createQName("{gsxhjsx}", "cm:content"), (Serializable)contentHeader); + + node.setProperties(properties); + + node.setType(QName.createQName("{gsxhjsx}", "trx:nsbbmbs")); + + List targetAssocs = new ArrayList(); + List sourceAssocs = new ArrayList(); + + targetAssocs.add(new AssociationRef(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "SA"), + QName.createQName("{gsxhjsx}", "app:super"), + new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "TA"))); + + + + sourceAssocs.add(new AssociationRef(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "HH"), + QName.createQName("{gsxhjsx}", "app:super"), + new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "JJ"))); + + node.setSourceAssocs(sourceAssocs); + node.setTargetAssocs(targetAssocs); + + formatter.writeTransferManifestNode(node); + + /** + * Write a second node + */ + + TransferManifestNormalNode node2 = new TransferManifestNormalNode(); + node2.setNodeRef(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "456")); + node2.setType(QName.createQName("{gsxhjsx}", "trx:dummy")); + formatter.writeTransferManifestNode(node2); + + + /** + * Write a deleted node + */ + TransferManifestDeletedNode node3 = new TransferManifestDeletedNode(); + node3.setNodeRef(new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, "567")); + + ChildAssociationRef origPrimaryParent = new ChildAssociationRef(QName.createQName("{gsxhjsx}", "cm:contains"), + new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "P1"), + QName.createQName("{gsxhjsx}", "app:whopper"), + new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "567"), + true, + -1); + + node3.setPrimaryParentAssoc(origPrimaryParent); + node3.setParentPath(new Path()); + + formatter.writeTransferManifestNode(node3); + + + formatter.endTransferManifest(); + snapshotWriter.close(); + + // + BufferedReader reader = new BufferedReader(new FileReader(snapshotFile)); + String s = reader.readLine(); + while(s != null) + { + System.out.println(s); + s = reader.readLine(); + } + + // Now try to parse the snapshot file we have just created + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + SAXParser parser = saxParserFactory.newSAXParser(); + + TransferManifestProcessor tp = new TestTransferManifestProcessor(); + + XMLTransferManifestReader xmlReader = new XMLTransferManifestReader(tp); + parser.parse(snapshotFile, xmlReader ); + + + + snapshotFile.delete(); + + } +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/TransferManifestWriter.java b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestWriter.java new file mode 100644 index 0000000000..a51d519bc4 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/TransferManifestWriter.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import java.io.Writer; +import java.util.Set; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; +import org.xml.sax.SAXException; + +/** + * Transfer Manifest Writer + * + * This class formats the transfer manifest and prints it to the specified writer + * + * It is a statefull object and writes one manifest at a time. + * + * Call start once, then write the header, then one or more nodes, then end. + * + */ +public interface TransferManifestWriter +{ + /** + * + * @param writer + * @throws SAXException + */ + void startTransferManifest(Writer writer) throws SAXException; + + /** + * + * @param header + * @throws SAXException + */ + void writeTransferManifestHeader(TransferManifestHeader header) throws SAXException; + + /** + * + * @param node + * @throws SAXException + */ + void writeTransferManifestNode(TransferManifestNode node) throws SAXException; + + /** + * + * @throws SAXException + */ + void endTransferManifest() throws SAXException; +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestReader.java b/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestReader.java new file mode 100644 index 0000000000..f4aa46815a --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestReader.java @@ -0,0 +1,596 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.transfer.PathHelper; +import org.alfresco.service.cmr.repository.Path; + +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.MLText; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO8601DateFormat; +import org.alfresco.util.ISO9075; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.Serializable; + +/** + * SAX XML Content Handler to read a transfer manifest XML Stream and + * delegate processing to the specified TransferManifestProcessor + * + * @author Mark Rogers + */ +public class XMLTransferManifestReader extends DefaultHandler implements ContentHandler, NamespacePrefixResolver +{ + private TransferManifestProcessor processor; + + /** + * These are the namespaces used within the document - there may be a different mapping to + * the namespaces of the Data Dictionary. + */ +// Map documentNamespaces = new HashMap(); + + final String TRANSFER_URI = ManifestModel.TRANSFER_MODEL_1_0_URI; + final String XMLNS_URI = "http://www.w3.org/XML/1998/namespace"; + LinkedList> namespaces = new LinkedList>(); + + public XMLTransferManifestReader(TransferManifestProcessor snapshotProcessor) + { + this.processor = snapshotProcessor; + + // prefix to uri map + HashMap namespace = new HashMap(); + namespace.put("xmlns", XMLNS_URI); + namespaces.add(namespace); + } + + public void startDocument() throws SAXException + { + processor.startTransferManifest(); + } + + public void endDocument() throws SAXException + { + processor.endTransferManifest(); + } + + public void characters(char[] ch, int start, int length) throws SAXException + { + if(buffer != null) + { + buffer.append(ch, start, length); + } + } + + /* + * Current State of the parser + */ + private StringBuffer buffer; + private Mapprops = new HashMap(); + + /** + * Start Element + */ + public void startElement(String uri, String localName, String prefixName, Attributes atts) + throws SAXException + { + QName elementQName = QName.resolveToQName(this, prefixName); + + HashMap namespace = new HashMap(); + namespaces.addFirst(namespace); + + /** + * Look for any namespace attributes + */ + for(int i = 0; i < atts.getLength(); i++) + { + QName attributeQName = QName.resolveToQName(this, atts.getQName(i)); + if(attributeQName.getNamespaceURI().equals(XMLNS_URI)) + { + namespace.put(attributeQName.getLocalName(), atts.getValue(i)); + } + } + + if(elementQName == null) + { + return; + } + + if(elementQName.getNamespaceURI().equals(TRANSFER_URI)); + { + // This is one of the transfer manifest elements + String elementName = elementQName.getLocalName(); + + // Simple and stupid parser for now + if(elementName.equals(ManifestModel.LOCALNAME_TRANSFER_MAINIFEST)) + { + // Good we got this + } + + if(elementName.equals(ManifestModel.LOCALNAME_TRANSFER_HEADER)) + { + TransferManifestHeader header = new TransferManifestHeader(); + props.put("header", header); + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_DELETED_NODE)) + { + TransferManifestDeletedNode node = new TransferManifestDeletedNode(); + NodeRef nodeRef = new NodeRef(atts.getValue("", "nodeRef")); + node.setNodeRef(nodeRef); + props.put("node", node); + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_NODE)) + { + TransferManifestNormalNode node = new TransferManifestNormalNode(); + NodeRef nodeRef = new NodeRef(atts.getValue("", "nodeRef")); + QName type = QName.createQName(atts.getValue("", "nodeType")); + node.setNodeRef(nodeRef); + node.setType(type); + props.put("node", node); + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_ASPECTS)) + { + TransferManifestNormalNode node = (TransferManifestNormalNode)props.get("node"); + node.setAspects(new HashSet()); + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_ASPECT)) + { + buffer = new StringBuffer(); + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_PROPERTIES)) + { + TransferManifestNormalNode node = (TransferManifestNormalNode)props.get("node"); + HashMapproperties = new HashMap(); + node.setProperties(properties); + props.put("properties", properties); + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_PROPERTY)) + { + QName name = QName.createQName(atts.getValue("", "name")); + props.put("name", name); + props.remove("values"); + props.remove("mltext"); + } + + if(elementName.equals(ManifestModel.LOCALNAME_HEADER_CREATED_DATE)) + { + buffer = new StringBuffer(); + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOCS)) + { + TransferManifestNormalNode node = (TransferManifestNormalNode)props.get("node"); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOCS)) + { + TransferManifestNormalNode node = (TransferManifestNormalNode)props.get("node"); + node.setChildAssocs(new ArrayList()); + + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOC)) + { + buffer = new StringBuffer(); + NodeRef to = new NodeRef(atts.getValue("", "to")); + QName type = QName.createQName(atts.getValue("", "type")); + Boolean isPrimary = Boolean.parseBoolean(atts.getValue("", "isPrimary")); + + props.put("to", to); + props.put("type", type); + props.put("isPrimary", isPrimary); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOC)) + { + buffer = new StringBuffer(); + NodeRef from = new NodeRef(atts.getValue("", "from")); + QName type = QName.createQName(atts.getValue("", "type")); + Boolean isPrimary = Boolean.parseBoolean(atts.getValue("", "isPrimary")); + props.put("from", from); + props.put("type", type); + props.put("isPrimary", isPrimary); + + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_TARGET_ASSOCS)) + { + TransferManifestNormalNode node = (TransferManifestNormalNode)props.get("node"); + List assocs = new ArrayList(); + node.setTargetAssocs(assocs); + props.put("assocs", assocs); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_SOURCE_ASSOCS)) + { + TransferManifestNormalNode node = (TransferManifestNormalNode)props.get("node"); + List assocs = new ArrayList(); + node.setSourceAssocs(assocs); + props.put("assocs", assocs); + + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_ASSOC)) + { + NodeRef source = new NodeRef(atts.getValue("", "source")); + NodeRef target = new NodeRef(atts.getValue("", "target")); + QName type = QName.createQName(atts.getValue("", "type")); + props.put("source", source); + props.put("target", target); + props.put("type", type); + + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_PRIMARY_PARENT)) + { + buffer = new StringBuffer(); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_VALUES)) + { + Collection values = new ArrayList(); + props.put("values", values); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_VALUE)) + { + buffer = new StringBuffer(); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_MLVALUE)) + { + MLText mltext = (MLText)props.get("mlvalues"); + if(mltext == null) + { + mltext = new MLText(); + props.put("mlvalues", mltext); + } + String strLocale = (String)atts.getValue("", "locale"); + Locale locale = I18NUtil.parseLocale(strLocale); + props.put("locale", locale); + buffer = new StringBuffer(); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_CONTENT_HEADER)) + { + String contentURL = (String)atts.getValue("", "contentURL"); + String mimetype = (String)atts.getValue("", "mimetype"); + String strLocale = (String)atts.getValue("", "locale"); + Locale locale = I18NUtil.parseLocale(strLocale); + String encoding = (String)atts.getValue("", "encoding"); + String sizeStr = (String)atts.getValue("", "size"); + Long size = Long.valueOf(sizeStr); + ContentData contentHeader = new ContentData(contentURL, mimetype, size.longValue(), encoding, locale); + props.put("contentHeader", contentHeader); + } + + + } + } // startElement + + /** + * End Element + */ + @SuppressWarnings("unchecked") + public void endElement(String uri, String localName, String prefixName) throws SAXException + { + namespaces.removeFirst(); + + QName elementQName = QName.resolveToQName(this, prefixName); + + if(elementQName == null) + { + return; + } + + if(elementQName.getNamespaceURI().equals(TRANSFER_URI)); + { + // This is one of the transfer manifest elements + String elementName = elementQName.getLocalName(); + + if(elementName.equals(ManifestModel.LOCALNAME_TRANSFER_MAINIFEST)) + { + } + + if(elementName.equals(ManifestModel.LOCALNAME_TRANSFER_HEADER)) + { + TransferManifestHeader header = (TransferManifestHeader)props.get("header"); + // User to process the header + processor.processTransferManifiestHeader(header); + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_NODE)) + { + TransferManifestNormalNode node = (TransferManifestNormalNode)props.get("node"); + processor.processTransferManifestNode(node); + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_DELETED_NODE)) + { + TransferManifestDeletedNode node = (TransferManifestDeletedNode)props.get("node"); + processor.processTransferManifestNode(node); + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_ASPECTS)) + { + + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_ASPECT)) + { + TransferManifestNormalNode node = (TransferManifestNormalNode)props.get("node"); + node.getAspects().add(QName.createQName(buffer.toString())); + buffer = null; + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_PROPERTIES)) + { + // nothing to do + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_PROPERTY)) + { + TransferManifestNormalNode node = (TransferManifestNormalNode)props.get("node"); + QName name = (QName)props.get("name"); + Serializable value = (Serializable)props.get("value"); + node.getProperties().put(name, value); + } + + if(elementName.equals(ManifestModel.LOCALNAME_HEADER_CREATED_DATE)) + { + TransferManifestHeader header = (TransferManifestHeader)props.get("header"); + header.setCreatedDate(ISO8601DateFormat.parse(buffer.toString())); + buffer = null; + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOCS)) + { + // No-op + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOCS)) + { + // No-op + } + + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOC)) + { + String value = buffer.toString(); + QName name = QName.createQName(value); + NodeRef to = (NodeRef)props.get("to"); + QName type = (QName) props.get("type"); + Boolean isPrimary = (Boolean)props.get("isPrimary"); + TransferManifestNormalNode node = (TransferManifestNormalNode)props.get("node"); + + ChildAssociationRef childAssociationRef = new ChildAssociationRef(type, node.getNodeRef(), name, to, isPrimary, -1); + node.getChildAssocs().add(childAssociationRef); + + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOC)) + { + String value = buffer.toString(); + QName name = QName.createQName(value); + NodeRef from = (NodeRef)props.get("from"); + QName type = (QName) props.get("type"); + Boolean isPrimary = (Boolean)props.get("isPrimary"); + TransferManifestNode node = (TransferManifestNode)props.get("node"); + + ChildAssociationRef childAssociationRef = new ChildAssociationRef(type, from, name, node.getNodeRef(), isPrimary, -1); + + if (TransferManifestNormalNode.class.isAssignableFrom(node.getClass())) + { + TransferManifestNormalNode normalNode = (TransferManifestNormalNode)node; + List parents = normalNode.getParentAssocs(); + if (parents == null) + { + parents = new ArrayList(); + normalNode.setParentAssocs(parents); + } + parents.add(childAssociationRef); + } + if (isPrimary) + { + node.setPrimaryParentAssoc(childAssociationRef); + } + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_TARGET_ASSOCS)) + { + //TransferManifestNode node = (TransferManifestNode)props.get("node"); + //node.getTargetAssocs().add((AssociationRef)props.get("assoc")); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_SOURCE_ASSOCS)) + { + //TransferManifestNode node = (TransferManifestNode)props.get("node"); + //node.getSourceAssocs().add((AssociationRef)props.get("assoc")); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_ASSOC)) + { + NodeRef source = (NodeRef)props.get("source"); + NodeRef target = (NodeRef)props.get("target"); + QName type = (QName) props.get("type"); + List assocs = (List)props.get("assocs"); + AssociationRef assoc = new AssociationRef(source, type, target); + assocs.add(assoc); + props.put("assoc", new AssociationRef(source, type, target)); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_PRIMARY_PATH)) + { + TransferManifestNode node = (TransferManifestNode)props.get("node"); + String value = buffer.toString(); + Path path = PathHelper.stringToPath(value); + node.setParentPath(path); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_VALUES)) + { + props.put("value", props.get("values")); + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_VALUE)) + { + Collection values = (Collection)props.get("values"); + String value = buffer.toString(); + + if(values != null) + { + values.add(value); + } + else + { + props.put("value", value); + } + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_MLVALUE)) + { + MLText mltext = (MLText)props.get("mlvalues"); + Locale locale = (Locale)props.get("locale"); + String value = buffer.toString(); + mltext.addValue(locale, value); + props.put("value", mltext); + + } + if(elementName.equals(ManifestModel.LOCALNAME_ELEMENT_CONTENT_HEADER)) + { + TransferManifestNode node = (TransferManifestNode)props.get("node"); + ContentData data = (ContentData)props.get("contentHeader"); + props.put("value", data); + } + } + } // end element + + + + public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException + { + //NO-OP + } + + public void processingInstruction(String target, String data) throws SAXException + { + //NO-OP + } + + public void setDocumentLocator(Locator locator) + { + //NO-OP + } + + public void skippedEntity(String name) throws SAXException + { + //NO-OP + } + + public void startPrefixMapping(String prefix, String uri) throws SAXException + { + HashMap namespace = namespaces.get(0); + // prefix is key, URI is value + namespace.put(prefix, uri); + } + + public void endPrefixMapping(String prefix) throws SAXException + { + HashMap namespace = namespaces.get(0); + // prefix is key, URI is value + namespace.remove(prefix); + } + + // Namespace Prefix Resolver implementation below + + /** + * lookup the prefix for a URI e.g. TRANSFER_URI for xfer + */ + public String getNamespaceURI(String prefix) throws NamespaceException + { + for(HashMap namespace : namespaces) + { + String uri = namespace.get(prefix); + if(uri != null) + { + return uri; + } + } + return null; + } + + /** + * @param uri + * @return the prefix + */ + public Collection getPrefixes(String namespaceURI) throws NamespaceException + { + Collection prefixes = new HashSet(); + + for(HashMap namespace : namespaces) + { + for(Entry entry : namespace.entrySet()) + { + if (namespaceURI.equals(entry.getValue())) + { + prefixes.add(entry.getKey()); + } + } + } + + return prefixes; + } + + public Collection getPrefixes() + { + Collection prefixes = new HashSet(); + + for(HashMap namespace : namespaces) + { + prefixes.addAll(namespace.keySet()); + } + + return prefixes; + } + + public Collection getURIs() + { + Collection uris = new HashSet(); + + for(HashMap namespace : namespaces) + { + uris.addAll(namespace.values()); + } + + return uris; + } +} diff --git a/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestWriter.java b/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestWriter.java new file mode 100644 index 0000000000..eefe8d091b --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/manifest/XMLTransferManifestWriter.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.manifest; + +import java.io.Serializable; +import java.io.Writer; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import org.alfresco.repo.transfer.TransferModel; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.MLText; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO8601DateFormat; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +/** + * XMLTransferManifestWriter + * + * Writes the transfer manifest out to XML format. + * + * @author Mark Rogers + */ +public class XMLTransferManifestWriter implements TransferManifestWriter +{ + public XMLTransferManifestWriter() + { + } + + private XMLWriter writer; + + final AttributesImpl EMPTY_ATTRIBUTES = new AttributesImpl(); + + final String PREFIX = ManifestModel.MANIFEST_PREFIX; + + /** + * Start the transfer manifest + */ + public void startTransferManifest(Writer writer) throws SAXException + { + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setNewLineAfterDeclaration(false); + format.setIndentSize(3); + format.setEncoding("UTF-8"); + + this.writer = new XMLWriter(writer, format); + this.writer.startDocument(); + + this.writer.startPrefixMapping(PREFIX, TransferModel.TRANSFER_MODEL_1_0_URI); + this.writer.startPrefixMapping("cm", NamespaceService.CONTENT_MODEL_1_0_URI); + + // Start Transfer Manifest // uri, name, prefix + this.writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_TRANSFER_MAINIFEST, PREFIX + ":" + ManifestModel.LOCALNAME_TRANSFER_MAINIFEST, EMPTY_ATTRIBUTES); + } + + /** + * End the transfer manifest + */ + public void endTransferManifest() throws SAXException + { + // End Transfer Manifest + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_TRANSFER_MAINIFEST, PREFIX + ":" + ManifestModel.LOCALNAME_TRANSFER_MAINIFEST); + writer.endPrefixMapping(PREFIX); + + writer.endDocument(); + } + + /** + * Write the transfer manifest header + */ + public void writeTransferManifestHeader(TransferManifestHeader header) throws SAXException + { + if(header.getCreatedDate() != null) + { + // Start Header + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_TRANSFER_HEADER, PREFIX + ":" + ManifestModel.LOCALNAME_TRANSFER_HEADER, EMPTY_ATTRIBUTES); + + // Created Date + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_HEADER_CREATED_DATE, PREFIX + ":" + ManifestModel.LOCALNAME_HEADER_CREATED_DATE, EMPTY_ATTRIBUTES); + writeDate(header.getCreatedDate()); + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_HEADER_CREATED_DATE, PREFIX + ":" + ManifestModel.LOCALNAME_HEADER_CREATED_DATE); + + // End Header + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_TRANSFER_HEADER, PREFIX + ":" + ManifestModel.LOCALNAME_TRANSFER_HEADER); + } + } + + /** + * Write a deleted node to the manifest file + * + * @param node + * @throws SAXException + */ + public void writeTransferManifestNode(TransferManifestDeletedNode node) throws SAXException + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute("uri", "nodeRef", "nodeRef", "String", node.getNodeRef().toString()); + + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_NODE, PREFIX + ":" +ManifestModel.LOCALNAME_ELEMENT_DELETED_NODE, attributes); + + if(node.getPrimaryParentAssoc() != null) + { + writePrimaryParent(node.getPrimaryParentAssoc(), node.getParentPath()); + } + + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_NODE, PREFIX + ":" +ManifestModel.LOCALNAME_ELEMENT_DELETED_NODE); + + } + + public void writeTransferManifestNode(TransferManifestNode node) throws SAXException + { + if(node instanceof TransferManifestDeletedNode) + { + TransferManifestDeletedNode node2 = (TransferManifestDeletedNode)node; + writeTransferManifestNode(node2); + } + else if(node instanceof TransferManifestNormalNode) + { + TransferManifestNormalNode node2 = (TransferManifestNormalNode)node; + writeTransferManifestNode(node2); + } + else + { + throw new IllegalArgumentException("Unexpected type" + node.getClass().getName()); + + } + } + + /** + * Write a normal transfer manifest node + * @param nodeRef + * @throws SAXException + */ + public void writeTransferManifestNode(TransferManifestNormalNode node) throws SAXException + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute("uri", "nodeRef", "nodeRef", "String", node.getNodeRef().toString()); + attributes.addAttribute("uri", "nodeType", "nodeType", "String", formatQName(node.getType())); + + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_NODE, PREFIX + ":" +ManifestModel.LOCALNAME_ELEMENT_NODE, attributes); + + if(node.getPrimaryParentAssoc() != null) + { + writePrimaryParent(node.getPrimaryParentAssoc(), node.getParentPath()); + } + + writeAspects(node.getAspects()); + + writeProperties(node.getProperties()); + + writeParentAssocs(node.getParentAssocs()); + + writeChildAssocs(node.getChildAssocs()); + + writeTargetAssocs(node.getTargetAssocs()); + + writeSourceAssocs(node.getSourceAssocs()); + + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_NODE, PREFIX + ":" +ManifestModel.LOCALNAME_ELEMENT_NODE); + } + + private void writeProperties(Map properties) throws SAXException + { + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PROPERTIES, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PROPERTIES, EMPTY_ATTRIBUTES); + if(properties != null ) + { + for(Entry entry : properties.entrySet()) + { + writeProperty(entry.getKey(), entry.getValue()); + } + } + + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PROPERTIES, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PROPERTIES); + } + + private void writeProperty(QName name, Serializable value) throws SAXException + { + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "name", "name", "String", formatQName(name)); + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PROPERTY, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PROPERTY, attributes); + } + + // TODO - Char [] does not seem to be processed correctly + if(value.getClass().isArray()) + { + writeValue(value.toString()); + } + // Collection + else if(value instanceof ContentData) + { + ContentData data = (ContentData)value; + AttributesImpl dataAttributes = new AttributesImpl(); + dataAttributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "contentURL","contentURL", "String", data.getContentUrl()); + dataAttributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "mimetype", "mimetype", "String", data.getMimetype()); + dataAttributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "size", "size", "String", Long.toString(data.getSize())); + dataAttributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "encoding", "encoding", "String", data.getEncoding()); + dataAttributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "locale", "locale", "String", data.getLocale().toString()); + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_CONTENT_HEADER, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_CONTENT_HEADER, dataAttributes); + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_CONTENT_HEADER, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_CONTENT_HEADER); + } + // Collection + else if(value instanceof Collection) + { + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_VALUES, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_VALUES, EMPTY_ATTRIBUTES); + int index = 0; + for (Object valueInCollection : (Collection)value) + { + writeValue((Serializable)valueInCollection); + index++; + } + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_VALUES, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_VALUES); + } + else if(value instanceof MLText) + { + MLText mltext = (MLText)value; + for(Entry entry : mltext.entrySet()) + { + writeMLValue(entry.getKey(), entry.getValue()); + } + } + else + { + writeValue(value); + } + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PROPERTY, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PROPERTY); + } + + private void writeValue(Serializable value) throws SAXException + { + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_VALUE, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_VALUE, EMPTY_ATTRIBUTES); + String strValue = (String)DefaultTypeConverter.INSTANCE.convert(String.class, value); + + writer.characters(strValue.toCharArray(), 0, strValue.length()); + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_VALUE, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_VALUE); + } + + private void writeMLValue(Locale locale, Serializable value) throws SAXException + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "locale", "locale", "String", locale.toString()); + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_MLVALUE, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_MLVALUE, attributes); + String strValue = (String)DefaultTypeConverter.INSTANCE.convert(String.class, value); + writer.characters(strValue.toCharArray(), 0, strValue.length()); + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_MLVALUE, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_MLVALUE); + } + + private void writeAspects(Set aspects) throws SAXException + { + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_ASPECTS, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_ASPECTS, EMPTY_ATTRIBUTES); + + if(aspects != null) + { + for(QName aspect : aspects) + { + writeAspect(aspect); + } + } + + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_ASPECTS, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_ASPECTS); + } + + private void writeAspect(QName aspect) throws SAXException + { + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_ASPECT, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_ASPECT, EMPTY_ATTRIBUTES); + String name=formatQName(aspect); + writer.characters(name.toCharArray(), 0, name.length()); + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_ASPECT, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_ASPECT); + } + + private void writeDate(Date date) throws SAXException + { + String dates = ISO8601DateFormat.format(date); + writer.characters(dates.toCharArray(), 0, dates.length()); + + } + + private String formatQName(QName qname) + { + return qname.toString(); + } + + private void writePrimaryParent(ChildAssociationRef parentAssoc, Path parentPath) throws SAXException + { + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PRIMARY_PARENT, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PRIMARY_PARENT, EMPTY_ATTRIBUTES); + + writeParentAssoc(parentAssoc); + + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PRIMARY_PATH, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PRIMARY_PATH, EMPTY_ATTRIBUTES); + if(parentPath != null) + { + String path = parentPath.toString(); + writer.characters(path.toCharArray(), 0, path.length()); + } + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PRIMARY_PATH, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PRIMARY_PATH); + + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PRIMARY_PARENT, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PRIMARY_PARENT); + } + + private void writeParentAssocs(List refs) throws SAXException + { + if(refs != null) + { + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOCS, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOCS, EMPTY_ATTRIBUTES); + + for(ChildAssociationRef assoc : refs) + { + writeParentAssoc(assoc); + } + + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOCS, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOCS); + } + } + private void writeChildAssocs(List refs) throws SAXException + { + if(refs != null) + { + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOCS, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOCS, EMPTY_ATTRIBUTES); + + for(ChildAssociationRef assoc : refs) + { + writeChildAssoc(assoc); + } + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOCS, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOCS); + } + } + + private void writeParentAssoc(ChildAssociationRef assoc) throws SAXException + { + if(assoc != null) + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "from", "from", "String", assoc.getParentRef().toString()); + attributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "type", "type", "String", formatQName(assoc.getTypeQName())); + attributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "type", "isPrimary", "Boolean", assoc.isPrimary()?"true":"false"); + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOC, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOC, attributes); + String name= formatQName(assoc.getQName()); + writer.characters(name.toCharArray(), 0, name.length()); + assoc.isPrimary(); + + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOC, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_PARENT_ASSOC); + } + } + + private void writeChildAssoc(ChildAssociationRef assoc) throws SAXException + { + if(assoc != null) + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "to", "to", "String", assoc.getChildRef().toString()); + attributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "type", "type", "String", formatQName(assoc.getTypeQName())); + attributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "type", "isPrimary", "Boolean", assoc.isPrimary()?"true":"false"); + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOC, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOC, attributes); + String name= formatQName(assoc.getQName()); + writer.characters(name.toCharArray(), 0, name.length()); + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOC, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_CHILD_ASSOC); + } + } + + private void writeTargetAssocs(List refs) throws SAXException + { + if(refs != null) + { + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_TARGET_ASSOCS, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_TARGET_ASSOCS, EMPTY_ATTRIBUTES); + + for(AssociationRef assoc : refs) + { + writeAssoc(assoc); + } + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_TARGET_ASSOCS, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_TARGET_ASSOCS); + } + } + private void writeSourceAssocs(List refs) throws SAXException + { + if(refs != null) + { + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_SOURCE_ASSOCS, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_SOURCE_ASSOCS, EMPTY_ATTRIBUTES); + + for(AssociationRef assoc : refs) + { + writeAssoc(assoc); + } + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_SOURCE_ASSOCS, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_SOURCE_ASSOCS); + } + } + private void writeAssoc(AssociationRef ref) throws SAXException + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "source", "source", "String", ref.getSourceRef().toString()); + attributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "target", "target", "String", ref.getTargetRef().toString()); + attributes.addAttribute(TransferModel.TRANSFER_MODEL_1_0_URI, "type", "type", "String", formatQName(ref.getTypeQName())); + + writer.startElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_ASSOC, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_ASSOC, attributes); + writer.endElement(TransferModel.TRANSFER_MODEL_1_0_URI, ManifestModel.LOCALNAME_ELEMENT_ASSOC, PREFIX + ":" + ManifestModel.LOCALNAME_ELEMENT_ASSOC); + } +} diff --git a/source/java/org/alfresco/repo/transfer/report/TransferReport.xsd b/source/java/org/alfresco/repo/transfer/report/TransferReport.xsd new file mode 100644 index 0000000000..8d29a4bd75 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/report/TransferReport.xsd @@ -0,0 +1,44 @@ + + + + + + + + This is the transfer report root tag + + + + + + + + + + + + + The destination of the transfer + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/transfer/report/TransferReportModel.java b/source/java/org/alfresco/repo/transfer/report/TransferReportModel.java new file mode 100644 index 0000000000..258dd6e845 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/report/TransferReportModel.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.report; + +import org.alfresco.repo.transfer.TransferModel; +import org.alfresco.service.namespace.QName; + +/** + * The transfer report model - extended for XML Manifest Model + */ +public interface TransferReportModel extends TransferModel +{ + static final String LOCALNAME_TRANSFER_REPORT = "transferReport"; + static final String LOCALNAME_TRANSFER_TARGET = "target"; + static final String LOCALNAME_TRANSFER_DEFINITION = "definition"; + static final String LOCALNAME_TRANSFER_EVENTS = "events"; + static final String LOCALNAME_TRANSFER_EVENT = "event"; + static final String REPORT_PREFIX = "report"; + + static final String TRANSFER_REPORT_MODEL_1_0_URI = "http://www.alfresco.org/model/transferReport/1.0"; +} diff --git a/source/java/org/alfresco/repo/transfer/report/TransferReporter.java b/source/java/org/alfresco/repo/transfer/report/TransferReporter.java new file mode 100644 index 0000000000..1bf86eb639 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/report/TransferReporter.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.report; + +import java.util.List; + +import org.alfresco.repo.transfer.Transfer; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.transfer.TransferDefinition; +import org.alfresco.service.cmr.transfer.TransferEvent; +import org.alfresco.service.cmr.transfer.TransferTarget; + +public interface TransferReporter +{ + /** + * Create a transfer report + * @param target the target of the transfer + * @param definition the definition of the transfer + * @param events the transfer events generated by the transfer. + * @return the node ref of the transfer report + */ + NodeRef createTransferReport(Transfer transfer, + TransferTarget target, + TransferDefinition definition, + List events); + +} diff --git a/source/java/org/alfresco/repo/transfer/report/TransferReporterImpl.java b/source/java/org/alfresco/repo/transfer/report/TransferReporterImpl.java new file mode 100644 index 0000000000..c2a63c0497 --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/report/TransferReporterImpl.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.transfer.report; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.transfer.Transfer; +import org.alfresco.repo.transfer.TransferModel; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.transfer.TransferDefinition; +import org.alfresco.service.cmr.transfer.TransferEvent; +import org.alfresco.service.cmr.transfer.TransferTarget; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyCheck; +import org.dom4j.io.XMLWriter; +import org.xml.sax.SAXException; + +public class TransferReporterImpl implements TransferReporter +{ + private NodeService nodeService; + private ContentService contentService; + + /** Default encoding **/ + private static String DEFAULT_ENCODING = "UTF-8"; + + public void init() + { + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "contentService", contentService); + } + + /** + * Create a new transfer report + * @return NodeRef the node ref of the new transfer report + */ + public NodeRef createTransferReport(Transfer transfer, + TransferTarget target, + TransferDefinition definition, + List events) + { + Map properties = new HashMap (); + String transferId = transfer.getTransferId(); + String title = "transfer report: success :" + transferId; + String name = transferId; + String description = "successful transfer report"; + + properties.put(ContentModel.PROP_NAME, name); + properties.put(ContentModel.PROP_TITLE, title); + properties.put(ContentModel.PROP_DESCRIPTION, description); + ChildAssociationRef ref = nodeService.createNode(target.getNodeRef(), ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), TransferModel.TYPE_TRANSFER_REPORT, properties); + ContentWriter writer = contentService.getWriter(ref.getChildRef(), ContentModel.PROP_CONTENT, true); + writer.setLocale(Locale.getDefault()); + writer.setMimetype(MimetypeMap.MIMETYPE_XML); + writer.setEncoding("UTF-8"); + + // + XMLTransferReportWriter reportWriter = new XMLTransferReportWriter(); + + BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(writer.getContentOutputStream())); + + try + { + reportWriter.startTransferReport("UTF-8", bufferedWriter); + + // Header + reportWriter.writeTarget(target); + + reportWriter.writeDefinition(definition); + + // Detail + reportWriter.writeTransferEvents(events); + + reportWriter.endTransferReport(); + + return ref.getChildRef(); + } + + catch (SAXException se) + { + return null; + } + finally + { + try + { + bufferedWriter.close(); + } + catch (IOException error) + { + error.printStackTrace(); + } + } + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public NodeService getNodeService() + { + return nodeService; + } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public ContentService getContentService() + { + return contentService; + } + +} diff --git a/source/java/org/alfresco/repo/transfer/report/XMLTransferReportWriter.java b/source/java/org/alfresco/repo/transfer/report/XMLTransferReportWriter.java new file mode 100644 index 0000000000..838222f56f --- /dev/null +++ b/source/java/org/alfresco/repo/transfer/report/XMLTransferReportWriter.java @@ -0,0 +1,119 @@ +package org.alfresco.repo.transfer.report; + +import java.io.Writer; +import java.util.Date; +import java.util.List; + +import org.alfresco.repo.transfer.TransferModel; +import org.alfresco.repo.transfer.manifest.ManifestModel; +import org.alfresco.repo.transfer.manifest.TransferManifestHeader; +import org.alfresco.service.cmr.transfer.TransferDefinition; +import org.alfresco.service.cmr.transfer.TransferEvent; +import org.alfresco.service.cmr.transfer.TransferTarget; +import org.alfresco.util.ISO8601DateFormat; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +public class XMLTransferReportWriter +{ + public XMLTransferReportWriter() + { + } + + private XMLWriter writer; + + final AttributesImpl EMPTY_ATTRIBUTES = new AttributesImpl(); + + final String PREFIX = TransferReportModel.REPORT_PREFIX; + + /** + * Start the transfer report + */ + public void startTransferReport(String encoding, Writer writer) throws SAXException + { + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setNewLineAfterDeclaration(false); + format.setIndentSize(3); + format.setEncoding(encoding); + + this.writer = new XMLWriter(writer, format); + this.writer.startDocument(); + + this.writer.startPrefixMapping(PREFIX, TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI); + + // Start Transfer Manifest // uri, name, prefix + this.writer.startElement(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, TransferReportModel.LOCALNAME_TRANSFER_REPORT, PREFIX + ":" + TransferReportModel.LOCALNAME_TRANSFER_REPORT, EMPTY_ATTRIBUTES); + } + + /** + * End the transfer report + */ + public void endTransferReport() throws SAXException + { + // End Transfer Manifest + writer.endElement(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, TransferReportModel.LOCALNAME_TRANSFER_REPORT, PREFIX + ":" + TransferReportModel.LOCALNAME_TRANSFER_REPORT); + writer.endPrefixMapping(PREFIX); + writer.endDocument(); + } + + /** + * Write the target to the report + */ + public void writeTarget(TransferTarget target) throws SAXException + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, "name", "name", "String", target.getName()); + attributes.addAttribute(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, "endpointHost", "endpointHost", "String", target.getEndpointHost()); + attributes.addAttribute(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, "endpointPort", "endpointPort", "int", String.valueOf(target.getEndpointPort())); + + writer.startElement(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, TransferReportModel.LOCALNAME_TRANSFER_TARGET, PREFIX + ":" + TransferReportModel.LOCALNAME_TRANSFER_TARGET, attributes); + writer.endElement(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, TransferReportModel.LOCALNAME_TRANSFER_TARGET, PREFIX + ":" + TransferReportModel.LOCALNAME_TRANSFER_TARGET); + + } + + /** + * Write the definition to the report + */ + public void writeDefinition(TransferDefinition definition) throws SAXException + { + writer.startElement(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, TransferReportModel.LOCALNAME_TRANSFER_DEFINITION, PREFIX + ":" + TransferReportModel.LOCALNAME_TRANSFER_DEFINITION, EMPTY_ATTRIBUTES); + writer.endElement(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, TransferReportModel.LOCALNAME_TRANSFER_DEFINITION, PREFIX + ":" + TransferReportModel.LOCALNAME_TRANSFER_DEFINITION); + + } + + /** + * Write the transfer manifest header + */ + public void writeTransferEvents(List events) throws SAXException + { + writer.startElement(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, TransferReportModel.LOCALNAME_TRANSFER_EVENTS, PREFIX + ":" + TransferReportModel.LOCALNAME_TRANSFER_EVENTS, EMPTY_ATTRIBUTES); + + for(TransferEvent event : events) + { + writeTransferEvent(event); + } + + writer.endElement(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, TransferReportModel.LOCALNAME_TRANSFER_EVENTS, PREFIX + ":" + TransferReportModel.LOCALNAME_TRANSFER_EVENTS); + + } + + /** + * Write the transfer manifest header + */ + public void writeTransferEvent(TransferEvent event) throws SAXException + { + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, "date", "date", "dateTime", ISO8601DateFormat.format(event.getTime())); + + writer.startElement(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, TransferReportModel.LOCALNAME_TRANSFER_EVENT, PREFIX + ":" + TransferReportModel.LOCALNAME_TRANSFER_EVENT, attributes); + + String message = event.getMessage(); + if(message != null) + { + writer.characters(message.toCharArray(), 0, message.length()); + } + writer.endElement(TransferReportModel.TRANSFER_REPORT_MODEL_1_0_URI, TransferReportModel.LOCALNAME_TRANSFER_EVENT, PREFIX + ":" + TransferReportModel.LOCALNAME_TRANSFER_EVENT); + } +} diff --git a/source/java/org/alfresco/service/cmr/repository/ContentData.java b/source/java/org/alfresco/service/cmr/repository/ContentData.java index 384566ad33..6add72b806 100644 --- a/source/java/org/alfresco/service/cmr/repository/ContentData.java +++ b/source/java/org/alfresco/service/cmr/repository/ContentData.java @@ -333,4 +333,17 @@ public class ContentData implements Serializable { return locale; } + + /** + * @return hashCode + */ + public int hashCode() + { + if(contentUrl!= null) + { + return contentUrl.hashCode(); + } + return 0; + } } + diff --git a/source/java/org/alfresco/service/cmr/repository/Path.java b/source/java/org/alfresco/service/cmr/repository/Path.java index c2fba32c4d..ae5fb063f2 100644 --- a/source/java/org/alfresco/service/cmr/repository/Path.java +++ b/source/java/org/alfresco/service/cmr/repository/Path.java @@ -41,7 +41,7 @@ import org.alfresco.util.ISO9075; * /x/y/z * * In the above example, there will be 4 elements, the first being a reference - * to the root node, followed by qname elements for x, x and z. + * to the root node, followed by qname elements for x, y and z. *

* Methods and constructors are available to construct a Path instance * from a path string or by building the path incrementally, including the ability to diff --git a/source/java/org/alfresco/service/cmr/transfer/NodeFilter.java b/source/java/org/alfresco/service/cmr/transfer/NodeFilter.java new file mode 100644 index 0000000000..251ec6a804 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/NodeFilter.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.service.cmr.transfer; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * @author brian + * + */ +public interface NodeFilter +{ + + /** + * Examines the supplied node and indicates whether it has been accepted by the filter. + * @param thisNode + * @param serviceRegistry + * @return true if the supplied node matches the criteria specified on this filter, and false + * otherwise. + */ + boolean accept(NodeRef thisNode); + + void init(); + + void setServiceRegistry(ServiceRegistry serviceRegistry); +} diff --git a/source/java/org/alfresco/service/cmr/transfer/NodeFinder.java b/source/java/org/alfresco/service/cmr/transfer/NodeFinder.java new file mode 100644 index 0000000000..f95657fbc2 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/NodeFinder.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.service.cmr.transfer; + +import java.util.Set; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * @author brian + * + */ +public interface NodeFinder +{ + + /** + * @param thisNode + * @param serviceRegistry + * @return + */ + Set findFrom(NodeRef thisNode); + + void init(); + + void setServiceRegistry(ServiceRegistry serviceRegistry); +} diff --git a/source/java/org/alfresco/service/cmr/transfer/RangedTransferEvent.java b/source/java/org/alfresco/service/cmr/transfer/RangedTransferEvent.java new file mode 100644 index 0000000000..cae7fd7287 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/RangedTransferEvent.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +/** + * A Ranged Transfer event is a detail record for a State that has many smaller steps. For example when sending content the first + * event is 1 of the number of files to send. The second is 2 of the number of files to send. + * + * These events are intended to support "progress bar" types of interfaces. + * + * @author Mark Rogers + */ +public interface RangedTransferEvent extends TransferEvent +{ + /** + * The position in the range + * @return + */ + long getPosition(); + + /** + * The maximum range + * @return + */ + long getRange(); + +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferCallback.java b/source/java/org/alfresco/service/cmr/transfer/TransferCallback.java new file mode 100644 index 0000000000..38cb1473aa --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferCallback.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +import java.io.Serializable; + +/** + * The transfer callback is called during a transfer, it allows the real-time feedback of + * an in progress transfer. It can be used to populate a deployment report or to display + * a user interface. + * + * @author Mark Rogers + */ +public interface TransferCallback extends Serializable +{ + /** + * processEvent + * @param event + */ + public void processEvent(TransferEvent event); + +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferDefinition.java b/source/java/org/alfresco/service/cmr/transfer/TransferDefinition.java new file mode 100644 index 0000000000..093cc4b576 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferDefinition.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +import java.io.Serializable; +import java.util.Set; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Definition of a transfer. + * + * Specifies which node to transfer + * + */ +public class TransferDefinition implements Serializable +{ + /** + * + */ + private static final long serialVersionUID = -8497919749300106861L; + + // Which nodes to deploy + private Set nodes; + + /** + * Set which nodes to transfer + * @param nodes + */ + public void setNodes(Set nodes) + { + this.nodes = nodes; + } + + /** + * Get which nodes to transfer + * @return + */ + public Set getNodes() + { + return nodes; + } +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferEvent.java b/source/java/org/alfresco/service/cmr/transfer/TransferEvent.java new file mode 100644 index 0000000000..4bb473c2f3 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferEvent.java @@ -0,0 +1,38 @@ +package org.alfresco.service.cmr.transfer; + +import java.util.Date; + +/** + * @author Mark Rogers + */ +public interface TransferEvent +{ + /** + * The transfer events will Start with a START event and finish with either SUCCESS or ERROR + */ + enum TransferState { START, SENDING_MANIFEST, SENDING_CONTENT, PREPARING, COMMITTING, SUCCESS, ERROR }; + + /** + * Get the state of this transfer + * @return the state of this transfer + */ + TransferState getTransferState(); + + /** + * The time this event occured. + * @return the date/time the event + */ + Date getTime(); + + /** + * Get a human readable message for this event + * @return + */ + String getMessage(); + + /** + * Is this the last event for this transfer ? + */ + boolean isLast(); + +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferEventCommittingStatus.java b/source/java/org/alfresco/service/cmr/transfer/TransferEventCommittingStatus.java new file mode 100644 index 0000000000..2f2121df77 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferEventCommittingStatus.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +import org.alfresco.repo.transfer.TransferEventImpl; + +/** + * TransferEventCommittingStatus are produced when a transfer is being committed. + * + * The range can be used to produce a "progress bar" + * + */ +public class TransferEventCommittingStatus extends TransferEventImpl implements RangedTransferEvent +{ + public String toString() + { + return "TransferEventCommittingStatus: " + super.getPosition() + " of " + super.getRange(); + } +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferEventEndState.java b/source/java/org/alfresco/service/cmr/transfer/TransferEventEndState.java new file mode 100644 index 0000000000..9d8367aec5 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferEventEndState.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +import org.alfresco.repo.transfer.TransferEventImpl; + +public class TransferEventEndState extends TransferEventImpl implements TransferEvent +{ + + public String toString() + { + return ("TransferEventEndState: " + super.toString()); + } +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferEventEnterState.java b/source/java/org/alfresco/service/cmr/transfer/TransferEventEnterState.java new file mode 100644 index 0000000000..090cd1dcc3 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferEventEnterState.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +import org.alfresco.repo.transfer.TransferEventImpl; + +public class TransferEventEnterState extends TransferEventImpl implements TransferEvent +{ + + public String toString() + { + return ("TransferEventEnterState: " + super.toString()); + } +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferEventError.java b/source/java/org/alfresco/service/cmr/transfer/TransferEventError.java new file mode 100644 index 0000000000..6c90b5d059 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferEventError.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +import org.alfresco.repo.transfer.TransferEventImpl; + +/** + * Indicates the reason why a transfer failed + */ +public class TransferEventError extends TransferEventImpl implements TransferEvent +{ + private Exception exception; + + public void setException(Exception exception) + { + this.exception = exception; + } + + public Exception getException() + { + return exception; + } + +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferEventSendingContent.java b/source/java/org/alfresco/service/cmr/transfer/TransferEventSendingContent.java new file mode 100644 index 0000000000..5f00e12d88 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferEventSendingContent.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +import org.alfresco.repo.transfer.TransferEventImpl; + +/** + * Ranged Transfer event for sending content (e.g. sending content 1 of 64) + */ +public class TransferEventSendingContent extends TransferEventImpl implements RangedTransferEvent +{ + private long size; + + public String toString() + { + return "TransferEventSendingContent: " + super.getPosition() + " of " + super.getRange(); + } + + public void setSize(long size) + { + this.size = size; + } + + public long getSize() + { + return size; + } + +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferEventSendingManifest.java b/source/java/org/alfresco/service/cmr/transfer/TransferEventSendingManifest.java new file mode 100644 index 0000000000..a092d225f0 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferEventSendingManifest.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +import org.alfresco.repo.transfer.TransferEventImpl; + +/** + * + * + */ +public class TransferEventSendingManifest extends TransferEventImpl implements RangedTransferEvent +{ + public String toString() + { + return "TransferEventSendingManifest: " + super.getPosition() + " of " + super.getRange(); + } + +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferEventSentContent.java b/source/java/org/alfresco/service/cmr/transfer/TransferEventSentContent.java new file mode 100644 index 0000000000..d8328fc32f --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferEventSentContent.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +import org.alfresco.repo.transfer.TransferEventImpl; + +public class TransferEventSentContent extends TransferEventImpl implements TransferEvent +{ + +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferEventSuccess.java b/source/java/org/alfresco/service/cmr/transfer/TransferEventSuccess.java new file mode 100644 index 0000000000..e2661c3c08 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferEventSuccess.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +import org.alfresco.repo.transfer.TransferEventImpl; + +/** + * The success event indicates a successfull transfer + */ +public class TransferEventSuccess extends TransferEventImpl implements TransferEvent +{ + public String toString() + { + return "TransferEventSuccess"; + } +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferException.java b/source/java/org/alfresco/service/cmr/transfer/TransferException.java new file mode 100644 index 0000000000..07701dabad --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferException.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.service.cmr.transfer; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Transfer service exception class + * + * @author Mark Rogers + */ +public class TransferException extends AlfrescoRuntimeException +{ + /** + * Serial version UID + */ + private static final long serialVersionUID = 3257571685241467958L; + + public TransferException(String msgId) + { + super(msgId); + } + + public TransferException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + public TransferException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } + + public TransferException(String msgId, Throwable cause) + { + super(msgId, cause); + } +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferReceiver.java b/source/java/org/alfresco/service/cmr/transfer/TransferReceiver.java new file mode 100644 index 0000000000..95307c0492 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferReceiver.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.service.cmr.transfer; + +import java.io.File; +import java.io.InputStream; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * @author brian + * + */ +public interface TransferReceiver +{ + /** + * + * @param transferId + * @return + */ + File getStagingFolder(String transferId); + + /** + * + * @param transferId + * @return + */ + NodeRef getTempFolder(String transferId); + + /** + * Asks the receiver to setup a new transfer. + * @return The identifier of the new transfer + * @throws TransferException if an error occurred while setting up the transfer + */ + String start() throws TransferException; + + /** + * Asks the receiver to end (and clean up) the specified transfer + * @param transferId The transfer to end + * @throws TransferException If the process of ending the transfer fails + */ + void end(String transferId) throws TransferException; + + /** + * Nudge the transfer lock (to prevent it expiring) if the supplied transferId matches that referenced by the lock. + * @param transferId + * @throws TransferException if the lock doesn't exist or doesn't correspond to the supplied transferId. + */ + void nudgeLock(String transferId) throws TransferException; + + /** + * Store the specified snapshot file into the transfer staging area. + * The specified transfer must currently be the holder of the transfer lock, otherwise an exception is thrown. + * This operation does not close the supplied stream, so the caller must do it as appropriate. The caller + * should assume that the supplied stream has been fully read when this operation returns. + * @param transferId The identifier of the transfer with which this snapshot is associated + * @param snapshotStream The open stream that holds the snapshot file. + * @throws TransferException If an error occurs while saving the snapshot file. + */ + void saveSnapshot(String transferId, InputStream snapshotStream) throws TransferException; + + void saveContent(String transferId, String contentId, InputStream contentStream) throws TransferException; + + /** + * Prepare + * @param transferId + * @throws TransferException + */ + void prepare(String transferId) throws TransferException; + + /** + * Abort + * @param transferId + * @throws TransferException + */ + void abort(String transferId) throws TransferException; + + /** + * Commit + * @param transferId + * @throws TransferException + */ + void commit(String transferId) throws TransferException; +} \ No newline at end of file diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferService.java b/source/java/org/alfresco/service/cmr/transfer/TransferService.java new file mode 100644 index 0000000000..fc7a392e83 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferService.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2009-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.service.cmr.transfer; + +import java.util.Set; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * The transfer service is responsible for transfering nodes between one instance of Alfresco and another remote instance. + * as well as the transfer method, this interface also provides methods for managing the + * + * @author Mark Rogers + */ +public interface TransferService +{ + + /** + * Transfer nodes, sync. This synchronous version of the transfer method waits for the transfer to complete + * before returning to the caller. Callbacks are called in the current thread context, so will be associated with the current + * transaction and user. + * + * @param targetName the name of the target to transfer to + * The following properties must be set, nodes + * @param definition, the definition of the transfer. Specifies which nodes to transfer. + * @throws TransferException + * @return the node reference of the transfer report + */ + public NodeRef transfer(String targetName, TransferDefinition definition) throws TransferException; + + /** + * Transfer nodes sync, with callback. This synchronous version of the transfer method waits for the transfer to complete + * before returning to the caller. Callbacks are called in the current thread context, so will be associated with the current + * transaction and user. + * + * @param targetName the name of the target to transfer to + * @param definition - the definition of the transfer. Specifies which nodes to transfer. + * The following properties must be set, nodes + * @param callback - a set of callback handlers that will be called as transfer proceeds. May be null. + * @throws TransferException + * @return the node reference of the transfer report + */ + public NodeRef transfer(String targetName, TransferDefinition definition, Set callback) throws TransferException; + + /** + * Transfer nodes async with callback. The asynchronous version of the transfer method starts a transfer and returns as + * soon as possible. + * The transfer callbacks will be called by a different thread to that used to call the transferAsync method so transaction + * context will be different to the calling context. + * + * Please also be aware that the asychronous transfer does not have access to uncommitted + * data in the calling transaction. + * + * @param targetName the name of the target to transfer to + * @param definition - the definition of the transfer. Specifies which nodes to transfer. + * The following properties must be set, nodes + * @param callback - a set of callback handlers that will be called as transfer proceeds. May be null. + * @throws TransferException + */ + public void transferAsync(String targetName, TransferDefinition definition, Set callback) throws TransferException; + + /** + * Verify a target is available and that the configured credentials correctly identify an admin user. + * @throws TransferException + */ + public void verify(TransferTarget target) throws TransferException; + + /** + * crate a new transfer target + * @param name, the name of this transfer target, which must be unique + * @param title, the display name of this transfer target + * @param description, + * @param endpointProtocol, either http or https + * @param endpointHost, + * @param endpointPort, + * @param endpointPath, + * @param username, + * @param password, + */ + public TransferTarget createTransferTarget(String name, String title, String description, String endpointProtocol, String endpointHost, int endpointPort, String endpointPath, String username, char[] password) throws TransferException; + + /** + * Get all the transfer targets + */ + public SetgetTransferTargets() throws TransferException; + + /** + * Get All the transfer targets for a particular transfer target group. + * @param groupName, the name of the transfer group + */ + public SetgetTransferTargets(String groupName) throws TransferException; + + /** + * Get a transfer target by its name + * @throws TransferException - target does not exist + */ + public TransferTarget getTransferTarget(String name) throws TransferException; + + /** + * Delete a transfer target. After calling this method the transfer target will no longer exist. + * @throws TransferException - target does not exist + * @param name, the name of this transfer target, + */ + public void deleteTransferTarget(String name) throws TransferException; + + /** + * Update TransferTarget + * The following properties may be updated: + * endpointHost, + * endpointPort, + * endpointProtocol, + * endpointPath, + * username, + * password, + * title, + * description + * + * The following properties may not be updated: + * name, must be specified. + * nodeRef, if specified will be ignored. + * + * @param update + */ + public TransferTarget updateTransferTarget(TransferTarget update) throws TransferException; + + /** + * Enables/Disables the named transfer target + * @param name the name of the transfer target + * @param enable (or false=disable) + */ + public void enableTransferTarget(String name, boolean enable) throws TransferException; + +} diff --git a/source/java/org/alfresco/service/cmr/transfer/TransferTarget.java b/source/java/org/alfresco/service/cmr/transfer/TransferTarget.java new file mode 100644 index 0000000000..7aa3c1f112 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/transfer/TransferTarget.java @@ -0,0 +1,135 @@ +package org.alfresco.service.cmr.transfer; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Transfer Target. + * + * @author Mark Rogers + */ +public interface TransferTarget +{ + /** + * read only - get the node reference of the underlying transfer target node. + * @return + */ + public NodeRef getNodeRef(); + + /** + * Get the name of this transfer target + * @return + */ + public String getName(); + + /** + * Set the name of this transfer target. Please note that you can't update the name of a + * transfer target. + * + * @param name + */ + public void setName(String name); + + /** + * Get the description for this transfer target + * @return + */ + public String getDescription(); + + /** + * Set the decription for this transfer target + * @param description + */ + public void setDescription(String description); + + /** + * Get the title of this transfer target + * @return + */ + String getTitle(); + + /** + * Set the title for this transfer target + * @param title + */ + public void setTitle(String title); + + /** + * Get the endpoint host + * @return + */ + public String getEndpointHost(); + + /** + * Set the endpoint host + * @param endpointHost + */ + public void setEndpointHost(String endpointHost); + + /** + * Get the endpoint port + * @return + */ + int getEndpointPort(); + + /** + * Set the endpoint port + * @param endpointPort + */ + public void setEndpointPort(int endpointPort); + + /** + * HTTP OR HTTPS + */ + public String getEndpointProtocol(); + + /** + * Set the endpoint protocol. + * @param endpointProtocol + */ + public void setEndpointProtocol(String endpointProtocol); + + /** + * Set the password for this transfer target + * @param password clear text password. + */ + public void setPassword(char[] password); + + /** + * The username used to authenticate with the transfer target + * @return + */ + String getUsername(); + + /** + * The username used to authenticate with the transfer target + * @param userName + */ + void setUsername(String userName); + + /** + * Get the cleartext password + * @return + */ + char[] getPassword(); + + /** + * The location of the transfer service on the target endpoint host + * @return + */ + String getEndpointPath(); + + /** + * The location of the transfer service on the target endpoint host + */ + void setEndpointPath(String path); + + /** + * is this transfer target enabled or disabled? + */ + boolean isEnabled(); + + /** + * enable this transfer target + */ + void setEnabled(boolean enabled); +} diff --git a/source/java/org/alfresco/service/namespace/QName.java b/source/java/org/alfresco/service/namespace/QName.java index 6ea494fd46..03431aff89 100644 --- a/source/java/org/alfresco/service/namespace/QName.java +++ b/source/java/org/alfresco/service/namespace/QName.java @@ -58,8 +58,10 @@ public final class QName implements QNamePattern, Serializable, Cloneable, Compa /** * Create a QName * + * (With no prefix) + * * @param namespaceURI the qualifying namespace (maybe null or empty string) - * @param localName the qualified name + * @param localName the local name * @return the QName */ public static QName createQName(String namespaceURI, String localName) @@ -109,7 +111,7 @@ public final class QName implements QNamePattern, Serializable, Cloneable, Compa /** - * Create a QName + * Create a QName (from prefix format) prefix:localName * * @param qname qualified name of the following format prefix:localName * @param prefixResolver lookup to resolve mappings between prefix and namespace diff --git a/source/java/org/alfresco/util/ThreadPoolExecutorFactoryBean.java b/source/java/org/alfresco/util/ThreadPoolExecutorFactoryBean.java index 300cde5e8d..069c473310 100644 --- a/source/java/org/alfresco/util/ThreadPoolExecutorFactoryBean.java +++ b/source/java/org/alfresco/util/ThreadPoolExecutorFactoryBean.java @@ -24,6 +24,7 @@ */ package org.alfresco.util; +import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; @@ -191,9 +192,11 @@ public class ThreadPoolExecutorFactoryBean implements FactoryBean, InitializingB if (workQueueSize < 0) { - workQueueSize = Integer.MAX_VALUE; + // Setting workQueueSize to MAX_VALUE prevents the pool growing and shrinking. See JavaDoc + // workQueueSize = Integer.MAX_VALUE; + workQueueSize = 1000; } - BlockingQueue workQueue = new LinkedBlockingQueue(workQueueSize); + BlockingQueue workQueue = new ArrayBlockingQueue(workQueueSize); // construct the instance instance = new ThreadPoolExecutor(