gt:         if (!callback.getMustCopy(sourceNodeTypeQName, copyDetails))
context: /*
   * #%L
   * Alfresco Repository
   * %%
   * Copyright (C) 2005 - 2016 Alfresco Software Limited
   * %%
   * This file is part of the Alfresco software. 
   * If the software was purchased under a paid Alfresco license, the terms of 
   * the paid license agreement will prevail.  Otherwise, the software is 
   * provided under the following open source license terms:
   * 
   * Alfresco is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   * 
   * Alfresco 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 Lesser General Public License for more details.
   * 
   * You should have received a copy of the GNU Lesser General Public License
   * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
   * #L%
   */
  package org.alfresco.repo.copy;
  
  import java.io.Serializable;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import org.alfresco.error.AlfrescoRuntimeException;
  import org.alfresco.model.ContentModel;
  import org.alfresco.query.CannedQuery;
  import org.alfresco.query.CannedQueryFactory;
  import org.alfresco.query.CannedQueryParameters;
  import org.alfresco.query.PagingRequest;
  import org.alfresco.query.PagingResults;
  import org.alfresco.repo.action.ActionServiceImpl;
  import org.alfresco.repo.copy.CopyBehaviourCallback.AssocCopySourceAction;
  import org.alfresco.repo.copy.CopyBehaviourCallback.AssocCopyTargetAction;
  import org.alfresco.repo.copy.CopyBehaviourCallback.ChildAssocCopyAction;
  import org.alfresco.repo.copy.CopyBehaviourCallback.ChildAssocRecurseAction;
  import org.alfresco.repo.copy.CopyBehaviourCallback.CopyAssociationDetails;
  import org.alfresco.repo.copy.CopyBehaviourCallback.CopyChildAssociationDetails;
  import org.alfresco.repo.copy.query.AbstractCopyCannedQueryFactory;
  import org.alfresco.repo.node.NodeServicePolicies;
  import org.alfresco.repo.policy.BehaviourFilter;
  import org.alfresco.repo.policy.ClassPolicyDelegate;
  import org.alfresco.repo.policy.JavaBehaviour;
  import org.alfresco.repo.policy.PolicyComponent;
  import org.alfresco.repo.transaction.TransactionalResourceHelper;
  import org.alfresco.service.cmr.dictionary.AspectDefinition;
  import org.alfresco.service.cmr.dictionary.AssociationDefinition;
  import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition;
  import org.alfresco.service.cmr.dictionary.ClassDefinition;
  import org.alfresco.service.cmr.dictionary.DictionaryService;
  import org.alfresco.service.cmr.dictionary.PropertyDefinition;
  import org.alfresco.service.cmr.dictionary.TypeDefinition;
  import org.alfresco.service.cmr.repository.AssociationRef;
  import org.alfresco.service.cmr.repository.ChildAssociationRef;
  import org.alfresco.service.cmr.repository.CopyService;
  import org.alfresco.service.cmr.repository.CopyServiceException;
  import org.alfresco.service.cmr.repository.NodeRef;
  import org.alfresco.service.cmr.repository.NodeService;
  import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
  import org.alfresco.service.cmr.rule.RuleService;
  import org.alfresco.service.cmr.security.AccessPermission;
  import org.alfresco.service.cmr.security.AccessStatus;
  import org.alfresco.service.cmr.security.PermissionService;
  import org.alfresco.service.cmr.security.PublicServiceAccessService;
  import org.alfresco.service.namespace.NamespaceService;
  import org.alfresco.service.namespace.QName;
  import org.alfresco.service.namespace.RegexQNamePattern;
  import org.alfresco.util.GUID;
  import org.alfresco.util.Pair;
  import org.alfresco.util.registry.NamedObjectRegistry;
  import org.apache.commons.io.FilenameUtils;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  import org.springframework.extensions.surf.util.I18NUtil;
  import org.springframework.extensions.surf.util.ParameterCheck;
  
  /**
   * Service implementation of copy operations.
   * 
   * @author Roy Wetherall
   * @author Derek Hulley
   */
  public class CopyServiceImpl extends AbstractBaseCopyService implements CopyService
  {
      private static Log logger = LogFactory.getLog(ActionServiceImpl.class);
      
      /* Query names */
      private static final String QUERY_FACTORY_GET_COPIES = "getCopiesCannedQueryFactory";
      
      /* I18N labels */
      private static final String COPY_OF_LABEL = "copy_service.copy_of_label";
      
      /* Services */
      private NodeService nodeService;
      private NodeService internalNodeService;
      private NamedObjectRegistry<CannedQueryFactory<CopyInfo>> cannedQueryRegistry;
      private DictionaryService dictionaryService;     
      private PolicyComponent policyComponent;
      private BehaviourFilter behaviourFilter;
      private RuleService ruleService;
      private PermissionService permissionService;
      private PublicServiceAccessService publicServiceAccessService;
  
      /* Policy delegates */
      private ClassPolicyDelegate<CopyServicePolicies.OnCopyNodePolicy> onCopyNodeDelegate;
      private ClassPolicyDelegate<CopyServicePolicies.OnCopyCompletePolicy> onCopyCompleteDelegate;
      private ClassPolicyDelegate<CopyServicePolicies.BeforeCopyPolicy> beforeCopyDelegate;
  
      public CopyServiceImpl()
      {
          super();
      }
  
      /**
       * @param nodeService  the node service
       */
      public void setNodeService(NodeService nodeService)
      {
          this.nodeService = nodeService;
      }
      
      /**
       * @param internalNodeService    the internal node service
       */
      public void setInternalNodeService(NodeService internalNodeService) 
      {
          this.internalNodeService = internalNodeService;
      }
      
      public void setCannedQueryRegistry(NamedObjectRegistry<CannedQueryFactory<CopyInfo>> cannedQueryRegistry)
      {
          this.cannedQueryRegistry = cannedQueryRegistry;
      }
  
      /**
       * @param dictionaryService  the dictionary service
       */
      public void setDictionaryService(DictionaryService dictionaryService) 
      {
          this.dictionaryService = dictionaryService;
      }
      
      /**
       * @param policyComponent  the policy component
       */
      public void setPolicyComponent(PolicyComponent policyComponent) 
      {
          this.policyComponent = policyComponent;
      }
  
      /**
       * @param behaviourFilter   used to disable specific behaviours while doing background tasks
       */
      public void setBehaviourFilter(BehaviourFilter behaviourFilter)
      {
          this.behaviourFilter = behaviourFilter;
      }
  
      /**
       * @param ruleService  the rule service
       */
      public void setRuleService(RuleService ruleService)
      {
          this.ruleService = ruleService;
      }
      
      /**
       * @param permissionService the permissionService to set
       */
      public void setPermissionService(PermissionService permissionService)
      {
          this.permissionService = permissionService;
      }
  
      /**
       * @param publicServiceAccessService the publicServiceAccessService to set
       */
      public void setPublicServiceAccessService(PublicServiceAccessService publicServiceAccessService)
      {
          this.publicServiceAccessService = publicServiceAccessService;
      }
  
      /**
       * Initialise method
       */
      public void init()
      {
          // Register the policies
          onCopyNodeDelegate = policyComponent.registerClassPolicy(CopyServicePolicies.OnCopyNodePolicy.class);
          onCopyCompleteDelegate = policyComponent.registerClassPolicy(CopyServicePolicies.OnCopyCompletePolicy.class);
          beforeCopyDelegate = policyComponent.registerClassPolicy(CopyServicePolicies.BeforeCopyPolicy.class);
          
          // Register policy behaviours
          this.policyComponent.bindAssociationBehaviour(
                  NodeServicePolicies.BeforeDeleteAssociationPolicy.QNAME,
                  ContentModel.ASPECT_COPIEDFROM,
                  ContentModel.ASSOC_ORIGINAL,
                  new JavaBehaviour(this, "beforeDeleteOriginalAssociation"));    
          this.policyComponent.bindClassBehaviour(
                  CopyServicePolicies.OnCopyNodePolicy.QNAME,
                  ContentModel.ASPECT_COPIEDFROM,
                  new JavaBehaviour(this, "getCallbackForCopiedFromAspect"));    
          this.policyComponent.bindClassBehaviour(
                  CopyServicePolicies.OnCopyNodePolicy.QNAME,
                  ContentModel.TYPE_FOLDER,
                  new JavaBehaviour(this, "getCallbackForFolderType"));    
          this.policyComponent.bindClassBehaviour(
                  CopyServicePolicies.OnCopyNodePolicy.QNAME,
                  ContentModel.ASPECT_OWNABLE,
                  new JavaBehaviour(this, "getCallbackForOwnableAspect"));
      }
      
      public NodeRef copy(
              NodeRef sourceNodeRef,
              NodeRef targetParentRef, 
              QName assocTypeQName,
              QName assocQName, 
              boolean copyChildren)
      {
          // Check that all the passed values are not null
          ParameterCheck.mandatory("sourceNodeRef", sourceNodeRef);
          ParameterCheck.mandatory("targetParentRef", targetParentRef);
          ParameterCheck.mandatory("assocTypeQName", assocTypeQName);
          ParameterCheck.mandatory("assocQName", assocQName);
  
          if (sourceNodeRef.getStoreRef().equals(targetParentRef.getStoreRef()) == false)
          {
              // Error - since at the moment we do not support cross store copying
              throw new UnsupportedOperationException("Copying nodes across stores is not currently supported.");
          }
  
          // ALF-17549: Transform Action causes the Path QName to change to "'cm:copy''
          QName sourceNodeTypeQName = nodeService.getType(sourceNodeRef);
  
          if (dictionaryService.isSubClass(sourceNodeTypeQName, ContentModel.TYPE_CONTENT) || dictionaryService.isSubClass(sourceNodeTypeQName, ContentModel.TYPE_FOLDER))
          {
              String newName = assocQName.getLocalName();
              String currentName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(sourceNodeRef, ContentModel.PROP_NAME));
              assocQName = getAssociationCopyInfo(nodeService, sourceNodeRef, null, newName, !currentName.equals(newName)).getTargetAssocQName();
          }
  
          // Clear out any record of copied associations
          TransactionalResourceHelper.getList(KEY_POST_COPY_ASSOCS).clear();
          
          // Keep track of copied children in order of copying
          Map<NodeRef, NodeRef> copiesByOriginals = new LinkedHashMap<NodeRef, NodeRef>(17);
          Set<NodeRef> copies = new HashSet<NodeRef>(17);
  
          NodeRef copiedNodeRef = copyImpl(
                  sourceNodeRef, targetParentRef,
                  assocTypeQName, assocQName,
                  copyChildren, true,                     // Drop cm:name for top-level node
                  copiesByOriginals, copies);
          // Check if the node was copied
          if (copiedNodeRef == null)
          {
              CopyDetails copyDetails = getCopyDetails(sourceNodeRef, targetParentRef, null, assocTypeQName, assocQName);
              // Denied!
              throw new CopyServiceException(
                      "A bound policy denied copy: \n" +
                      "   " + copyDetails);
          }
          
          // Copy an associations that were left until now
          copyPendingAssociations(copiesByOriginals);
          
          // Foreach of the newly created copies call the copy complete policy
          for (Map.Entry<NodeRef, NodeRef> entry : copiesByOriginals.entrySet())
          {
              NodeRef mappedSourceNodeRef = entry.getKey();
              NodeRef mappedTargetNodeRef = entry.getValue();
              invokeCopyComplete(mappedSourceNodeRef, mappedTargetNodeRef, true, copiesByOriginals);
          }
          
          // Done
          return copiedNodeRef;
      }
      
      public NodeRef copyAndRename(
              NodeRef sourceNodeRef,
              NodeRef destinationParent,
              QName assocTypeQName,
              QName assocQName, 
              boolean copyChildren) 
      {
          // To fix ETWOONE-224 issue it is necessary to change a QName of the new node accordingly to its name.
          NodeRef result = null;
          String sourceName = (String)this.internalNodeService.getProperty(sourceNodeRef, ContentModel.PROP_NAME);
                  
          // Find a non-duplicate name
          String newName = sourceName;
  
          // rename top-level node if it should be renamed in process of copy
          QName qname = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(newName));
          String newNameAfterCopy = getTopLevelNodeNewName(sourceNodeRef, destinationParent, assocTypeQName, qname);
          if (newNameAfterCopy != null && !newNameAfterCopy.equals(newName))
          {
              newName = newNameAfterCopy;
          }
  
          while (this.internalNodeService.getChildByName(destinationParent, assocTypeQName, newName) != null)
          {
              newName = buildNewName(newName);
          }
                  
          if (assocQName == null)
          {
              // Change a QName of the new node accordingly to its name
              assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(newName));
          }
          
          // Make a copy
          result = copy(sourceNodeRef, destinationParent, assocTypeQName, assocQName, copyChildren);
          
          // Set name property
          this.internalNodeService.setProperty(result, ContentModel.PROP_NAME, newName);
          
          // Return new NodeRef
          return result;    
      }
      
      /**
       * {@inheritDoc}
       * 
       * Defer to the standard implementation with copyChildren set to false
       */
      public NodeRef copy(
              NodeRef sourceNodeRef,
              NodeRef targetParentNodeRef, 
              QName assocTypeQName,
              QName assocQName)
      {
          return copy(
                  sourceNodeRef, 
                  targetParentNodeRef, 
                  assocTypeQName, 
                  assocQName, 
                  false);
      }
  
      /**
       * {@inheritDoc}
       * 
       * Defer to the standard implementation with copyChildren set to false
       */
      public void copy(NodeRef sourceNodeRef, NodeRef targetNodeRef)
      {
          QName sourceNodeTypeQName = nodeService.getType(sourceNodeRef);
          QName targetNodeTypeQName = nodeService.getType(targetNodeRef);
          // Check that the source and destination node are the same type
          if (!sourceNodeTypeQName.equals(targetNodeTypeQName))
          {
              // Error - can not copy objects that are of different types
              throw new CopyServiceException("The source and destination node must be the same type.");
          }
          
          // Get the destinations node's details
          ChildAssociationRef destinationPrimaryAssocRef = nodeService.getPrimaryParent(targetNodeRef);
          NodeRef destinationParentNodeRef = destinationPrimaryAssocRef.getParentRef();
          QName assocTypeQName = destinationPrimaryAssocRef.getTypeQName();
          QName assocQName = destinationPrimaryAssocRef.getQName();
          
          // Get the copy details
          CopyDetails copyDetails = getCopyDetails(
                  sourceNodeRef, destinationParentNodeRef, targetNodeRef,
                  assocTypeQName, assocQName);
          
          // Get callbacks
          Map<QName, CopyBehaviourCallback> callbacks = getCallbacks(copyDetails);
          
          // invoke the before copy policy
          invokeBeforeCopy(sourceNodeRef, targetNodeRef);
          
          // Clear out any record of copied associations
          TransactionalResourceHelper.getList(KEY_POST_COPY_ASSOCS).clear();
          
          // Copy 
          copyProperties(copyDetails, targetNodeRef, sourceNodeTypeQName, callbacks);
          copyAspects(copyDetails, targetNodeRef, Collections.<QName>emptySet(), callbacks);
          copyResidualProperties(copyDetails, targetNodeRef);
          
          Map<NodeRef, NodeRef> copiedNodeRefs = new HashMap<NodeRef, NodeRef>(1);
          copiedNodeRefs.put(sourceNodeRef, targetNodeRef);
  
          Set<NodeRef> copies = new HashSet<NodeRef>(5);
          copyChildren(
                  copyDetails,
                  targetNodeRef,
                  false,                              // We know that the node has been created
                  true,
                  copiedNodeRefs,
                  copies,
                  callbacks);
          
          // Copy an associations that were left until now
          copyPendingAssociations(copiedNodeRefs);
          // invoke the copy complete policy
          invokeCopyComplete(sourceNodeRef, targetNodeRef, false, copiedNodeRefs);         
      }
      
      @Override
      public NodeRef getOriginal(NodeRef nodeRef)
      {
          List<AssociationRef> assocs = internalNodeService.getTargetAssocs(nodeRef, ContentModel.ASSOC_ORIGINAL);
          if (assocs.size() > 1)
          {
              logger.warn("Multiple cm:orignal associations from node: " + nodeRef);
          }
          if (assocs.size() == 0)
          {
              return null;
          }
          else
          {
              return assocs.get(0).getTargetRef();
          }
      }
  
      @Override
      public List<NodeRef> getCopies(NodeRef nodeRef)
      {
          PagingRequest pagingRequest = new PagingRequest(1000);
          PagingResults<CopyInfo> page = getCopies(nodeRef, pagingRequest);
          if (page.hasMoreItems())
          {
              logger.warn("Trimmed page size for deprecated getCopies() call.");
          }
          List<CopyInfo> pageResults = page.getPage();
          List<NodeRef> results = new ArrayList<NodeRef>(pageResults.size());
          for (CopyInfo copyInfo : pageResults)
          {
              results.add(copyInfo.getNodeRef());
          }
          return results;
      }
      
      @Override
      public PagingResults<CopyInfo> getCopies(NodeRef originalNodeRef, PagingRequest pagingRequest)
      {
          CannedQueryFactory<CopyInfo> queryFactory = cannedQueryRegistry.getNamedObject(QUERY_FACTORY_GET_COPIES);
          CannedQueryParameters params = new CannedQueryParameters(
                  new AbstractCopyCannedQueryFactory.CopyCannedQueryDetail(originalNodeRef),
                  null,
                  pagingRequest);
          CannedQuery<CopyInfo> query = queryFactory.getCannedQuery(params);
          return query.execute();
      }
  
      @Override
      public PagingResults<CopyInfo> getCopies(
              NodeRef originalNodeRef,
              NodeRef copyParentNodeRef,
              PagingRequest pagingRequest)
      {
          CannedQueryFactory<CopyInfo> queryFactory = cannedQueryRegistry.getNamedObject(QUERY_FACTORY_GET_COPIES);
          CannedQueryParameters params = new CannedQueryParameters(
                  new AbstractCopyCannedQueryFactory.CopyCannedQueryDetail(originalNodeRef, copyParentNodeRef),
                  null,
                  pagingRequest);
          CannedQuery<CopyInfo> query = queryFactory.getCannedQuery(params);
          return query.execute();
      }
  
      /**
       * @return                  Returns <tt>null</tt> if the node was denied a copy
       */
      private NodeRef copyImpl(
              NodeRef sourceNodeRef,
              NodeRef targetParentRef,
              QName assocTypeQName,
              QName assocQName, 
              boolean copyChildren,
              boolean dropName,
              Map<NodeRef, NodeRef> copiesByOriginals,
              Set<NodeRef> copies)
      {
          // Build the top-level node's copy details
          CopyDetails copyDetails = getCopyDetails(sourceNodeRef, targetParentRef, null, assocTypeQName, assocQName);
          
          // Get the callbacks that will determine the copy behaviour
          Map<QName, CopyBehaviourCallback> callbacks = getCallbacks(copyDetails);
          
          // Check that the primary (type) callback allows copy
          QName sourceNodeTypeQName = copyDetails.getSourceNodeTypeQName();
          CopyBehaviourCallback callback = callbacks.get(sourceNodeTypeQName);
          if (callback == null)
          {
              throw new IllegalStateException("Source node type has no callback: " + sourceNodeTypeQName);
          }
  