(
                AssocCopySourceAction.COPY_REMOVE_EXISTING,
                AssocCopyTargetAction.USE_COPIED_TARGET);
    }
    /**
     * @return      Returns ChildAssocRecurseAction.RESPECT_RECURSE_FLAG
     */
    public ChildAssocRecurseAction getChildAssociationRecurseAction(
            QName classQName,
            CopyDetails copyDetails,
            CopyChildAssociationDetails childAssocCopyDetails)
    {
        return ChildAssocRecurseAction.RESPECT_RECURSE_FLAG;
    }
    
    /**
     * @throws      IllegalStateException  always
     */
    protected void throwExceptionForUnexpectedBehaviour(CopyDetails copyDetails, String ... otherDetails)
    {
        StringBuilder sb = new StringBuilder(512);
        sb.append("Behaviour should have been invoked: \n" +
                "   Aspect: " + this.getClass().getName() + "\n" +
                "   " + copyDetails + "\n");
        for (String otherDetail : otherDetails)
        {
            sb.append("   ").append(otherDetail).append("\n");
        }
        throw new IllegalStateException(sb.toString());
    }
    
    /**
     * Helper method to transactionally record NodeRef properties so that they
     * can later be fixed up to point to the relative, after-copy locations.
     * 
     * When the copy has been completed, the second stage of the process can be applied.
     * 
     * @param sourceNodeRef             the node that is being copied
     * @param properties                the node properties being copied
     * @param propertyQName             the qualified name of the property to check
     * 
     * @see #repointNodeRefs(NodeRef, NodeRef, QName, Map, NodeService)
     */
    public void recordNodeRefsForRepointing(
            NodeRef sourceNodeRef,
            Map properties,
            QName propertyQName)
    {
        Serializable parameterValue = properties.get(propertyQName);
        if (parameterValue != null &&
                (parameterValue instanceof Collection> || parameterValue instanceof NodeRef))
        {
            String key = KEY_NODEREF_REPOINTING_PREFIX + propertyQName.toString();
            // Store it for later
            Map map = TransactionalResourceHelper.getMap(key);
            map.put(sourceNodeRef, parameterValue);
        }
    }
    
    /**
     * The second stage of the NodeRef repointing.  Call this method to have
     * any NodeRef properties readjusted to reflect the copied node hierarchy.
     * Only use this method if it a requirement for the particular type or aspect that you
     * are coding for.
     * 
     * @param sourceNodeRef         the source node
     * @param propertyQName         the target node i.e. the copy of the source node
     * @param copyMap               the full hierarchy copy map of source to copies
     * 
     * @see #recordNodeRefsForRepointing(NodeRef, Map, QName)
     */
    @SuppressWarnings("unchecked")
    public void repointNodeRefs(
            NodeRef sourceNodeRef,
            NodeRef targetNodeRef,
            QName propertyQName,
            Map copyMap,
            NodeService nodeService)
    {
        String key = KEY_NODEREF_REPOINTING_PREFIX + propertyQName.toString();
        Map map = TransactionalResourceHelper.getMap(key);
        Serializable value = map.get(sourceNodeRef);
        if (value == null)
        {
            // Don't bother.  The source node did not have a NodeRef property
            return;
        }
        Serializable newValue = null;
        if (value instanceof Collection)
        {
            Collection oldList = (Collection) value;
            List newList = new ArrayList(oldList.size());
            for (Serializable oldListValue : oldList)
            {
                Serializable newListValue = oldListValue;
                if (oldListValue instanceof NodeRef)
                {
                    newListValue = repointNodeRef(copyMap, (NodeRef) oldListValue);
                }
                // Put the value in the new list even though the new list might be discarded
                newList.add(newListValue);
                // Check if the value changed
                if (!newListValue.equals(oldListValue))
                {
                    // The value changed, so the new list will have to be set onto the target node
                    newValue = (Serializable) newList;
                }
            }
        }
        else if (value instanceof NodeRef)
        {
            NodeRef newNodeRef = repointNodeRef(copyMap, (NodeRef) value);
            if (!newNodeRef.equals(value))
            {
                // The value changed, so the new list will have to be set onto the target node
                newValue = newNodeRef;
            }
        }
        else
        {
            throw new IllegalStateException("Should only have Collections and NodeRef values");
        }
        // Fix the node property on the target, if necessary
        if (newValue != null)
        {
            nodeService.setProperty(targetNodeRef, propertyQName, newValue);
        }
    }
    
    private NodeRef repointNodeRef(Map copyMap, NodeRef pointerNodeRef)
    {
        NodeRef copiedPointerNodeRef = copyMap.get(pointerNodeRef);
        if (copiedPointerNodeRef == null)
        {
            return pointerNodeRef;
        }
        else
        {
            return copiedPointerNodeRef;
        }
    }
    
    /**
     * By default it is forbidden for top-level nodes to be renamed
     */
    @Override
    public boolean isTopLevelCanBeRenamed(QName classQName, CopyDetails copyDetails)
    {
        return false;
    }
}