permissions = permissionService.getAllSetPermissions(sourceNodeRef);
            boolean includeInherited = permissionService.getInheritParentPermissions(sourceNodeRef);
            if((publicServiceAccessService.hasAccess("PermissionService", "setPermission", destinationNodeRef, "dummyAuth", "dummyPermission", true) == AccessStatus.ALLOWED) &&
                    (publicServiceAccessService.hasAccess("PermissionService", "setInheritParentPermissions", destinationNodeRef, includeInherited) == AccessStatus.ALLOWED))
            {
                // Set the permission values on the destination node        
                for (AccessPermission permission : permissions) 
                {
                    if(permission.isSetDirectly())
                    {
                        permissionService.setPermission(
                                destinationNodeRef, 
                                permission.getAuthority(), 
                                permission.getPermission(), 
                                permission.getAccessStatus().equals(AccessStatus.ALLOWED));
                    }
                }
                permissionService.setInheritParentPermissions(destinationNodeRef, includeInherited);
            }
        }
    }
    /**
     * Gets the copy details.  This calls the appropriate policies that have been registered
     * against the node and aspect types in order to pick-up any type specific copy behaviour.
     * 
     * The full {@link NodeService} is used for property retrieval.  After this, read permission
     * can be assumed to have passed on the source node - at least w.r.t. properties and aspects.
     * 
     * NOTE: If a target node is not supplied, then one is created in the same store as the
     *              target parent node.  This allows behavioural code always know which node will
     *              be copied to, even if the node does not exist.
     */
    private CopyDetails getCopyDetails(
            NodeRef sourceNodeRef,
            NodeRef targetParentNodeRef,
            NodeRef targetNodeRef,
            QName assocTypeQName,
            QName assocQName)
    {
        // The first call will fail permissions, so there is no point doing permission checks with
        // the other calls
        QName sourceNodeTypeQName = nodeService.getType(sourceNodeRef);
        // ALF-730: MLText is not fully carried during cut-paste or copy-paste
        //          Use the internalNodeService to fetch the properties.  It should be mlAwareNodeService.
        Map sourceNodeProperties = internalNodeService.getProperties(sourceNodeRef);
        Set sourceNodeAspectQNames = internalNodeService.getAspects(sourceNodeRef);
        
        // Create a target node, if necessary
        boolean targetNodeIsNew = false;
        if (targetNodeRef == null)
        {
            targetNodeRef = new NodeRef(targetParentNodeRef.getStoreRef(), GUID.generate());
            targetNodeIsNew = true;
        }
        
        CopyDetails copyDetails = new CopyDetails(
                sourceNodeRef,
                sourceNodeTypeQName,
                sourceNodeAspectQNames,
                sourceNodeProperties,
                targetParentNodeRef,
                targetNodeRef,
                targetNodeIsNew,
                assocTypeQName,
                assocQName);
        
        // Done
        return copyDetails;
    }
    
    /**
     * @return         Returns a map of all the copy behaviours keyed by type and aspect qualified names
     */
    private Map getCallbacks(CopyDetails copyDetails)
    {
        QName sourceNodeTypeQName = copyDetails.getSourceNodeTypeQName();
        
        Map callbacks = new HashMap(11);
        // Get the type-specific behaviour
        CopyBehaviourCallback callback = getCallback(sourceNodeTypeQName, copyDetails);
        callbacks.put(sourceNodeTypeQName, callback);
        
        // Get the source aspects
        for (QName sourceNodeAspectQName : copyDetails.getSourceNodeAspectQNames()) 
        {
            callback = getCallback(sourceNodeAspectQName, copyDetails);
            callbacks.put(sourceNodeAspectQName, callback);
        }
        
        return callbacks;
    }
    
    /**
     * @return             Returns the copy callback for the given criteria
     */
    private CopyBehaviourCallback getCallback(QName sourceClassQName, CopyDetails copyDetails)
    {
        Collection policies = this.onCopyNodeDelegate.getList(sourceClassQName);
        ClassDefinition sourceClassDef = dictionaryService.getClass(sourceClassQName);
        CopyBehaviourCallback callback = null;
        if (sourceClassDef == null)
        {
            // Do nothing as the type is not in the dictionary
            callback = DoNothingCopyBehaviourCallback.getInstance();
        }
        if (policies.isEmpty())
        {
            // Default behaviour
            callback = DefaultCopyBehaviourCallback.getInstance();
        }
        else if (policies.size() == 1)
        {
            callback = policies.iterator().next().getCopyCallback(sourceClassQName, copyDetails);
        }
        else
        {
            // There are multiple
            CompoundCopyBehaviourCallback compoundCallback = new CompoundCopyBehaviourCallback(sourceClassQName);
            for (CopyServicePolicies.OnCopyNodePolicy policy : policies)
            {
                CopyBehaviourCallback nestedCallback = policy.getCopyCallback(sourceClassQName, copyDetails);
                compoundCallback.addBehaviour(nestedCallback);
            }
            callback = compoundCallback;
        }
        // Done
        if (logger.isDebugEnabled())
        {
            logger.debug(
                    "Fetched copy callback: \n" +
                    "   Class:                      " + sourceClassQName + "\n" +
                    "   Details:                    " + copyDetails + "\n" +
                    "   Callback: " + callback);
        }
        return callback;
    }
    
    /**
     * Copies the properties for the node type or aspect onto the destination node.
     */
    private void copyProperties(
            CopyDetails copyDetails, 
            NodeRef targetNodeRef,
            QName classQName,
            Map callbacks)
    {
        ClassDefinition targetClassDef = dictionaryService.getClass(classQName);
        if (targetClassDef == null)
        {
            return;                        // Ignore unknown types
        }
        // First check if the aspect must be copied at all
        CopyBehaviourCallback callback = callbacks.get(classQName);
        if (callback == null)
        {
            throw new IllegalStateException("Source node class has no callback: " + classQName);
        }
        // Ignore if not present or if not scheduled for a copy
        if (!callback.getMustCopy(classQName, copyDetails))
        {
            // Do nothing with this
            return;
        }
        // Compile the properties to copy, even if they are empty
        Map classProperties = buildCopyProperties(
                copyDetails,
                Collections.singleton(classQName),
                callbacks);
        // We don't need permissions as we've just created the node
        if (targetClassDef.isAspect())
        {
            internalNodeService.addAspect(targetNodeRef, classQName, classProperties);
        }
        else
        {
            internalNodeService.addProperties(targetNodeRef, classProperties);
        }
    }
    
    /**
     * Copy properties that do not belong to the source node's type or any of the aspects.
     */
    private void copyResidualProperties(
            CopyDetails copyDetails,
            NodeRef targetNodeRef)
    {
        Map residualProperties = new HashMap();
        // Start with the full set
        residualProperties.putAll(copyDetails.getSourceNodeProperties());
        
        QName sourceNodeTypeQName = copyDetails.getSourceNodeTypeQName();
        Set knownClassQNames = new HashSet(13);
        // We add the default aspects, source-applied aspects and the source node type
        knownClassQNames.addAll(getDefaultAspects(sourceNodeTypeQName));
        knownClassQNames.addAll(copyDetails.getSourceNodeAspectQNames());
        knownClassQNames.add(sourceNodeTypeQName);
        
        for (QName knownClassQName : knownClassQNames)
        {
            ClassDefinition classDef = dictionaryService.getClass(knownClassQName);
            if (classDef == null)
            {
                continue;
            }
            // Remove defined properties form the residual list
            for (QName definedPropQName : classDef.getProperties().keySet())
            {
                residualProperties.remove(definedPropQName);
                // We've removed them all, so shortcut out
                if (residualProperties.size() == 0)
                {
                    break;
                }
            }
        }
        // Add the residual properties to the node
        if (residualProperties.size() > 0)
        {
            internalNodeService.addProperties(targetNodeRef, residualProperties);
        }
    }
    
    /**
     * Copies aspects from the source to the target node.
     */
    private void copyAspects(
            CopyDetails copyDetails, 
            NodeRef targetNodeRef,
            Set aspectsToIgnore,
            Map callbacks)
    {
        Set sourceAspectQNames = copyDetails.getSourceNodeAspectQNames();
        for (QName aspectQName : sourceAspectQNames)
        {
            if (aspectsToIgnore.contains(aspectQName))
            {
                continue;
            }
            
            // Double check that the aspect must be copied at all
            CopyBehaviourCallback callback = callbacks.get(aspectQName);
            if (callback == null)
            {
                throw new IllegalStateException("Source aspect class has no callback: " + aspectQName);
            }
            if (!callback.getMustCopy(aspectQName, copyDetails))
            {
                continue;
            }
            copyProperties(copyDetails, targetNodeRef, aspectQName, callbacks);
        }
    }
    
    /**
     * @param copyChildren              false if the client selected not to recurse
     */
    private void copyChildren(
            CopyDetails copyDetails,
            NodeRef copyTarget,
            boolean copyTargetIsNew,
            boolean copyChildren,
            Map copiesByOriginals,
            Set copies,
            Map callbacks)
    {
        QName sourceNodeTypeQName = copyDetails.getSourceNodeTypeQName();
        Set sourceNodeAspectQNames = copyDetails.getSourceNodeAspectQNames();
        // First check associations on the type
        copyChildren(
                copyDetails,
                sourceNodeTypeQName,
                copyTarget,
                copyTargetIsNew,
                copyChildren,
                copiesByOriginals,
                copies,
                callbacks);
        // Check associations for the aspects
        for (QName aspectQName : sourceNodeAspectQNames)
        {
            AspectDefinition aspectDef = dictionaryService.getAspect(aspectQName);
            if (aspectDef == null)
            {
                continue;
            }
            copyChildren(
                    copyDetails,
                    aspectQName,
                    copyTarget,
                    copyTargetIsNew,
                    copyChildren,
                    copiesByOriginals,
                    copies,
                    callbacks);
        }
    }
    private static final String KEY_POST_COPY_ASSOCS = "CopyServiceImpl.postCopyAssocs";
    /**
     * @param copyChildren              false if the client selected not to recurse
     */
    private void copyChildren(
            CopyDetails copyDetails,
            QName classQName,
            NodeRef copyTarget,
            boolean copyTargetIsNew,
            boolean copyChildren,
            Map copiesByOriginals,
            Set copies,
            Map callbacks)
    {
        NodeRef sourceNodeRef = copyDetails.getSourceNodeRef();
        
        ClassDefinition classDef = dictionaryService.getClass(classQName);
        if (classDef == null)
        {
            // Ignore missing types
            return;
        }
        // Check the behaviour
        CopyBehaviourCallback callback = callbacks.get(classQName);
        if (callback == null)
        {
            throw new IllegalStateException("Source node class has no callback: " + classQName);
        }
        
        // Prepare storage for post-copy association handling
        List> postCopyAssocs =
                TransactionalResourceHelper.getList(KEY_POST_COPY_ASSOCS);
        
        // Handle peer associations.
        for (Map.Entry entry : classDef.getAssociations().entrySet())
        {
            QName assocTypeQName = entry.getKey();
            AssociationDefinition assocDef = entry.getValue();
            if (assocDef.isChild())
            {
                continue;                   // Ignore child assocs
            }
            boolean haveRemovedFromCopyTarget = false;
            // Get the associations
            List assocRefs = nodeService.getTargetAssocs(sourceNodeRef, assocTypeQName);
            for (AssociationRef assocRef : assocRefs)
            {
                // Get the copy action for the association instance
                CopyAssociationDetails assocCopyDetails = new CopyAssociationDetails(
                        assocRef,
                        copyTarget,
                        copyTargetIsNew);
                Pair assocCopyAction = callback.getAssociationCopyAction(
                        classQName,
                        copyDetails,
                        assocCopyDetails);
                
                // Consider the source side first
                switch (assocCopyAction.getFirst())
                {
                    case IGNORE:
                        continue;                       // Do nothing
                    case COPY_REMOVE_EXISTING:
                        if (!copyTargetIsNew && !haveRemovedFromCopyTarget)
                        {
                            // Only do this if we are copying over an existing node and we have NOT
                            // already cleaned up for this association type
                            haveRemovedFromCopyTarget = true;
                            for (AssociationRef assocToRemoveRef : internalNodeService.getTargetAssocs(copyTarget, assocTypeQName))
                            {
                                internalNodeService.removeAssociation(assocToRemoveRef.getSourceRef(), assocToRemoveRef.getTargetRef(), assocTypeQName);
                            }
                        }
                        // Fall through to copy
                    case COPY:
                        // Record the type of target behaviour that is expected
                        switch (assocCopyAction.getSecond())
                        {
                            case USE_ORIGINAL_TARGET:
                            case USE_COPIED_TARGET:
                            case USE_COPIED_OTHERWISE_ORIGINAL_TARGET:
                                // Have to save for later to see if the target node is copied, too
                                postCopyAssocs.add(new Pair(assocRef, assocCopyAction.getSecond()));
                                break;
                            default:
                                throw new IllegalStateException("Unknown association target copy action: " + assocCopyAction);
                        }
                        break;
                    default:
                        throw new IllegalStateException("Unknown association source copy action: " + assocCopyAction);
                }
            }
        }
        
        // Handle child associations.  These need special attention due to their recursive nature.
        for (Map.Entry childEntry : classDef.getChildAssociations().entrySet())
        {
            QName childAssocTypeQName = childEntry.getKey();
            ChildAssociationDefinition childAssocDef = childEntry.getValue();
            if (!childAssocDef.isChild())
            {
                continue;                   // Ignore non-child assocs
            }
            // Get the child associations
            List childAssocRefs = nodeService.getChildAssocs(
                    sourceNodeRef, childAssocTypeQName, RegexQNamePattern.MATCH_ALL);
            for (ChildAssociationRef childAssocRef : childAssocRefs)
            {
                NodeRef childNodeRef = childAssocRef.getChildRef();
                QName assocQName = childAssocRef.getQName();
                
                CopyChildAssociationDetails childAssocCopyDetails = new CopyChildAssociationDetails(
                        childAssocRef,
                        copyTarget,
                        copyTargetIsNew,
                        copyChildren);
                
                // Handle nested copies
                if (copies.contains(childNodeRef))
                {
                    // The node was already copied i.e. we are seeing a copy produced by some earlier
                    // copy process.
                    // The first way this can occur is if a hierarchy is copied into some lower part
                    // of the hierarchy.  We avoid the copied part.
                    // The other way this could occur is if there are multiple assocs between a
                    // parent and child.  Calls to this method are scoped by class, so the newly-created
                    // node will not be found because it will have been created using a different assoc
                    // type.
                    // A final edge case is where there are multiple assocs between parent and child
                    // of the same type.  This is ignorable.
                    continue;
                }
                // Get the copy action for the association instance
                ChildAssocCopyAction childAssocCopyAction = callback.getChildAssociationCopyAction(
                        classQName,
                        copyDetails,
                        childAssocCopyDetails);
                switch (childAssocCopyAction)
                {
                case IGNORE:
                    break;
                case COPY_ASSOC:
                    nodeService.addChild(copyTarget, childNodeRef, childAssocTypeQName, assocQName);
                    break;
                case COPY_CHILD:
                    // Handle potentially cyclic relationships
                    if (copiesByOriginals.containsKey(childNodeRef))
                    {
                        // This is either a cyclic relationship or there are multiple different
                        // types of associations between the same parent and child.
                        // Just hook the child up with the association.
                        nodeService.addChild(copyTarget, childNodeRef, childAssocTypeQName, assocQName);
                    }
                    else
                    {
                        // Find out whether to force a recursion
                        ChildAssocRecurseAction childAssocRecurseAction = callback.getChildAssociationRecurseAction(
                                classQName,
                                copyDetails,
                                childAssocCopyDetails);
                        switch (childAssocRecurseAction)
                        {
                        case RESPECT_RECURSE_FLAG:
                            // Keep child copy flag the same
                            break;
                        case FORCE_RECURSE:
                            // Force recurse
                            copyChildren = true;
                            break;
                        default:
                            throw new IllegalStateException("Unrecognized enum");
                        }
                        // This copy may fail silently
                        copyImpl(
                                childNodeRef, copyTarget,
                                childAssocTypeQName, assocQName,
                                copyChildren, false,                // Keep child names for deep copies
                                copiesByOriginals, copies);
                    }
                    break;
                default:
                    throw new IllegalStateException("Unrecognized enum");
                }
            }
        }
    }
    /**
     * Callback behaviour for the 'original' assoc ('copiedfrom' aspect).
     */
    public void beforeDeleteOriginalAssociation(AssociationRef nodeAssocRef)
    {
        // Remove the cm:copiedfrom aspect
        NodeRef sourceNodeRef = nodeAssocRef.getSourceRef();
        // We are about to modify a copied node.  For this specific action, we do not
        // want to leave any trace of the action as it's a task invisible to the end-user.
        try
        {
            behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_LOCKABLE);
            behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
            internalNodeService.removeAspect(sourceNodeRef, ContentModel.ASPECT_COPIEDFROM);
        }
        finally
        {
            behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_LOCKABLE);
            behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
        }
    }
    
    /**
     * Callback behaviour retrieval for the 'copiedfrom' aspect.
     * 
     * @return              Returns {@link DoNothingCopyBehaviourCallback} always
     */
    public CopyBehaviourCallback getCallbackForCopiedFromAspect(QName classRef, CopyDetails copyDetails)
    {
        return DoNothingCopyBehaviourCallback.getInstance();
    }
    
    /**
     * Callback behaviour retrieval for {@link ContentModel#TYPE_FOLDER} aspect.
     * 
     * @return              Returns {@link FolderTypeCopyBehaviourCallback}
     */
    public CopyBehaviourCallback getCallbackForFolderType(QName classRef, CopyDetails copyDetails)
    {
        return FolderTypeCopyBehaviourCallback.INSTANCE;
    }
    /**
     * cm:folder behaviour
     * 
     * @author Derek Hulley
     * @since 3.2
     */
    private static class FolderTypeCopyBehaviourCallback extends DefaultCopyBehaviourCallback
    {
        private static final CopyBehaviourCallback INSTANCE = new FolderTypeCopyBehaviourCallback();
        /**
         * Respects the copyChildren flag.  Child nodes are copied fully if the association
         * is primary otherwise secondary associations are duplicated.
         */
        @Override
        public ChildAssocCopyAction getChildAssociationCopyAction(
                QName classQName,
                CopyDetails copyDetails,
                CopyChildAssociationDetails childAssocCopyDetails)
        {
            ChildAssociationRef childAssocRef = childAssocCopyDetails.getChildAssocRef();
            boolean copyChildren = childAssocCopyDetails.isCopyChildren();
            if (childAssocRef.getTypeQName().equals(ContentModel.ASSOC_CONTAINS))
            {
                if (!copyChildren)
                {
                    return ChildAssocCopyAction.IGNORE;
                }
                if (childAssocRef.isPrimary())
                {
                    return ChildAssocCopyAction.COPY_CHILD;
                }
                else
                {
                    return ChildAssocCopyAction.COPY_ASSOC;
                }
            }
            else
            {
                throw new IllegalStateException(
                        "Behaviour should have been invoked: \n" +
                        "   Aspect: " + this.getClass().getName() + "\n" +
                        "   Assoc:  " + childAssocRef + "\n" +
                        "   " + copyDetails);
            }
        }
        
    }
    
    /**
     * Callback behaviour retrieval for the 'ownable' aspect.
     * 
     * @return              Returns {@link DoNothingCopyBehaviourCallback} always
     */
    public CopyBehaviourCallback getCallbackForOwnableAspect(QName classRef, CopyDetails copyDetails)
    {
        return DoNothingCopyBehaviourCallback.getInstance();
    }
    /**
     * Determines if top-level node name will be changed during copy according to policies.
     */
    @Override
    public String getTopLevelNodeNewName(NodeRef sourceNodeRef, NodeRef targetParentRef, QName assocTypeQName, QName assocQName)
    {
        // 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 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);
        }
        if (!callback.getMustCopy(sourceNodeTypeQName, copyDetails))
        {
            // Denied!
            return null;
        }
        if (callback.isTopLevelCanBeRenamed(sourceNodeTypeQName, copyDetails))
        {
            // Get the type properties to copy
            Map targetNodeProperties = buildCopyProperties(
                    copyDetails,
                    Collections.singleton(sourceNodeTypeQName),
                    callbacks);
            String newName = (String) targetNodeProperties.get(ContentModel.PROP_NAME);
            String oldName = (String) copyDetails.getSourceNodeProperties().get(ContentModel.PROP_NAME);
            if (oldName.equals(newName))
            {
                newName = null;
            }
            return newName;
        }
        return null;
    }
}