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
+ */
+ List