/*
 * #%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.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.transaction.UserTransaction;

import junit.framework.TestCase;

import org.alfresco.model.ContentModel;
import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
import org.alfresco.repo.action.evaluator.NoConditionEvaluator;
import org.alfresco.repo.action.executer.AddFeaturesActionExecuter;
import org.alfresco.repo.action.executer.CopyActionExecuter;
import org.alfresco.repo.action.executer.MoveActionExecuter;
import org.alfresco.repo.action.executer.TransformActionExecuter;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.dictionary.M2Aspect;
import org.alfresco.repo.dictionary.M2Association;
import org.alfresco.repo.dictionary.M2ChildAssociation;
import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.dictionary.M2Property;
import org.alfresco.repo.dictionary.M2Type;
import org.alfresco.repo.node.integrity.IntegrityChecker;
import org.alfresco.repo.rule.RuleModel;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
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.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.CopyService;
import org.alfresco.service.cmr.repository.CopyService.CopyInfo;
import org.alfresco.service.cmr.repository.MLText;
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.rule.Rule;
import org.alfresco.service.cmr.rule.RuleService;
import org.alfresco.service.cmr.rule.RuleType;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
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.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.Pair;
import org.alfresco.util.PropertyMap;
import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext;
import org.springframework.extensions.surf.util.I18NUtil;

/**
 * Unit tests for copy service
 * 
 * @author Roy Wetherall
 * @author Derek Hulley
 */
@Category(OwnJVMTestsCategory.class)
public class CopyServiceImplTest extends TestCase
{
    private ApplicationContext ctx;
    
    /*
     * Services used by the tests
     */
    private TransactionService transactionService;
    private NodeService nodeService;
    private NodeService publicNodeService;
    private CopyService copyService;
    private DictionaryDAO dictionaryDAO;
    private ContentService contentService;
    private RuleService ruleService;
    private ActionService actionService;
    private PermissionService permissionService;
    private PersonService personService;
    private AuthenticationComponent authenticationComponent;
    private MutableAuthenticationService authenticationService;
    private CheckOutCheckInService cociService;
    
    /*
     * Data used by the tests
     */
    private UserTransaction txn;
    private StoreRef storeRef;
    private NodeRef sourceNodeRef;    
    private NodeRef rootNodeRef;    
    private NodeRef targetNodeRef;
    private NodeRef nonPrimaryChildNodeRef;
    private NodeRef childNodeRef;
    private NodeRef destinationNodeRef;
    
    /*
     * Types and properties used by the tests
     */
    private static final String TEST_TYPE_NAMESPACE = "testTypeNamespaceURI";
    private static final QName TEST_TYPE_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testType");
    private static final QName PROP1_QNAME_MANDATORY = QName.createQName(TEST_TYPE_NAMESPACE, "prop1Mandatory");
    private static final QName PROP2_QNAME_OPTIONAL = QName.createQName(TEST_TYPE_NAMESPACE, "prop2Optional");
    
    private static final QName TEST_ASPECT_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testAspect");
    private static final QName PROP3_QNAME_MANDATORY = QName.createQName(TEST_TYPE_NAMESPACE, "prop3Mandatory");
    private static final QName PROP4_QNAME_OPTIONAL = QName.createQName(TEST_TYPE_NAMESPACE, "prop4Optional");
    
    private static final QName PROP_QNAME_MY_NODE_REF = QName.createQName(TEST_TYPE_NAMESPACE, "myNodeRef");
    private static final QName PROP_QNAME_MY_ANY = QName.createQName(TEST_TYPE_NAMESPACE, "myAny");    
    
    private static final QName PROP_QNAME_RESIDUAL_NODE_REF = QName.createQName(TEST_TYPE_NAMESPACE, "residualNodeRef");
    private static final QName PROP_QNAME_RESIDUAL_ANY = QName.createQName(TEST_TYPE_NAMESPACE, "residualAny"); 
    
    private static final QName TEST_MANDATORY_ASPECT_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testMandatoryAspect");
    private static final QName PROP5_QNAME_MANDATORY = QName.createQName(TEST_TYPE_NAMESPACE, "prop5Mandatory");
    
    private static final String TEST_NAME = "testName";
    private static final String TEST_VALUE_1 = "testValue1";
    private static final String TEST_VALUE_2 = "testValue2";
    private static final String TEST_VALUE_3 = "testValue3";
    
    private static final QName TEST_CHILD_ASSOC_TYPE_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "contains");
    private static final QName TEST_CHILD_ASSOC_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testChildAssocName");
    private static final QName TEST_ASSOC_TYPE_QNAME = QName.createQName(TEST_TYPE_NAMESPACE, "testAssocName");
    private static final QName TEST_CHILD_ASSOC_QNAME2 = QName.createQName(TEST_TYPE_NAMESPACE, "testChildAssocName2");
    
    private static final ContentData CONTENT_DATA_TEXT = new ContentData(null, "text/plain", 0L, "UTF-8");
    
    private static final String USER_1 = "User1";
    private static final String USER_2 = "User2";
    
    private static final QName TYPE_CUSTOM_CMIS_DOCUMENT = QName.createQName("{http://www.alfresco.org/model/cmis/custom}document");
    private static final QName PROP_CUSTOM_STRING = QName.createQName("{http://www.alfresco.org/model/cmis/custom}docprop_string");


    /**
     * Test content
     */
    private static final String SOME_CONTENT = "This is some content ...";    
    
    @Override
    protected void setUp() throws Exception
    {
        ctx = ApplicationContextHelper.getApplicationContext();
        if (AlfrescoTransactionSupport.isActualTransactionActive())
        {
            fail("Test started with transaction in progress");
        }
        
        ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
        // Set the services
        transactionService = serviceRegistry.getTransactionService();
        nodeService = (NodeService) ctx.getBean("dbNodeService");
        publicNodeService = serviceRegistry.getNodeService();
        copyService = (CopyService) ctx.getBean("copyService");
        contentService = (ContentService) ctx.getBean("contentService");
        ruleService = (RuleService) ctx.getBean("ruleService");
        actionService = (ActionService)ctx.getBean("actionService");
        permissionService = (PermissionService)ctx.getBean("PermissionService");
        personService = serviceRegistry.getPersonService();
        authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent");
        authenticationService = (MutableAuthenticationService) ctx.getBean("authenticationService");
        cociService = (CheckOutCheckInService) ctx.getBean("checkOutCheckInService");
        dictionaryDAO = (DictionaryDAO) ctx.getBean("dictionaryDAO");
        
        authenticationComponent.setSystemUserAsCurrentUser();
        
        // Ensure that a transaction is present
        txn = transactionService.getUserTransaction();
        txn.begin();
        
        // Create the test model
        createTestModel();
        
        // Create the store and get the root node reference
        storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis());
        rootNodeRef = nodeService.getRootNode(storeRef);
        
        // Create the node used for copying
        ChildAssociationRef childAssocRef = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}test"),
                TEST_TYPE_QNAME,
                createTypePropertyBag());
        sourceNodeRef = childAssocRef.getChildRef();
        
        // Create another bag of properties
        Map<QName, Serializable> aspectProperties = new HashMap<QName, Serializable>();
        aspectProperties.put(PROP3_QNAME_MANDATORY, TEST_VALUE_1);
        aspectProperties.put(PROP4_QNAME_OPTIONAL, TEST_VALUE_2);
        
        // Apply the test aspect
        nodeService.addAspect(
                sourceNodeRef, 
                TEST_ASPECT_QNAME, 
                aspectProperties);
        
        nodeService.addAspect(sourceNodeRef, ContentModel.ASPECT_TITLED, null);
        
        // Add a child
        ChildAssociationRef temp3 =nodeService.createNode(
                sourceNodeRef, 
                TEST_CHILD_ASSOC_TYPE_QNAME, 
                TEST_CHILD_ASSOC_QNAME, 
                TEST_TYPE_QNAME, 
                createTypePropertyBag());
        childNodeRef = temp3.getChildRef();
        
        // Add a child that is primary
        ChildAssociationRef temp2 = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}testNonPrimaryChild"),
                TEST_TYPE_QNAME,
                createTypePropertyBag());
        
        nonPrimaryChildNodeRef = temp2.getChildRef();
        nodeService.addChild(
                sourceNodeRef,
                nonPrimaryChildNodeRef,
                TEST_CHILD_ASSOC_TYPE_QNAME,
                TEST_CHILD_ASSOC_QNAME2);
        
        // Add a target assoc
        ChildAssociationRef temp = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}testAssoc"),
                TEST_TYPE_QNAME,
                createTypePropertyBag());
        targetNodeRef = temp.getChildRef();
        nodeService.createAssociation(sourceNodeRef, targetNodeRef, TEST_ASSOC_TYPE_QNAME);
        
        // Create a node we can use as the destination in a copy
        Map<QName, Serializable> destinationProps = new HashMap<QName, Serializable>();
        destinationProps.put(PROP1_QNAME_MANDATORY, TEST_VALUE_1);            
        destinationProps.put(PROP5_QNAME_MANDATORY, TEST_VALUE_3); 
        destinationProps.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT);
        ChildAssociationRef temp5 = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}testDestinationNode"),
                TEST_TYPE_QNAME,
                destinationProps);
        destinationNodeRef = temp5.getChildRef();
        
        // Create two users, for use as part of
        //  the permission related tests
        authenticationService.createAuthentication(USER_1, "PWD".toCharArray());
        authenticationService.createAuthentication(USER_2, "PWD".toCharArray());
        
        PropertyMap personProperties = new PropertyMap();
        personProperties.put(ContentModel.PROP_USERNAME, USER_1);
        personProperties.put(ContentModel.PROP_AUTHORITY_DISPLAY_NAME, "title" + USER_1);
        personProperties.put(ContentModel.PROP_FIRSTNAME, "firstName");
        personProperties.put(ContentModel.PROP_LASTNAME, "lastName");
        personProperties.put(ContentModel.PROP_EMAIL, USER_1+"@example.com");
        personProperties.put(ContentModel.PROP_JOBTITLE, "jobTitle");
        personService.createPerson(personProperties);
        
        personProperties = new PropertyMap();
        personProperties.put(ContentModel.PROP_USERNAME, USER_2);
        personProperties.put(ContentModel.PROP_AUTHORITY_DISPLAY_NAME, "title" + USER_2);
        personProperties.put(ContentModel.PROP_FIRSTNAME, "firstName");
        personProperties.put(ContentModel.PROP_LASTNAME, "lastName");
        personProperties.put(ContentModel.PROP_EMAIL, USER_2+"@example.com");
        personProperties.put(ContentModel.PROP_JOBTITLE, "jobTitle");
        personService.createPerson(personProperties);
    }
    
    @Override
    protected void tearDown() throws Exception
    {
        if (txn != null)
        {
            try { txn.rollback(); } catch (Throwable e) {}
        }
        authenticationComponent.clearCurrentSecurityContext();
    }
    
    /**
     * Helper method that creates a bag of properties for the test type
     * 
     * @return  bag of properties
     */
    private Map<QName, Serializable> createTypePropertyBag()
    {
        Map<QName, Serializable> result = new HashMap<QName, Serializable>();
        result.put(ContentModel.PROP_NAME, TEST_NAME);
        result.put(PROP1_QNAME_MANDATORY, TEST_VALUE_1);
        result.put(PROP2_QNAME_OPTIONAL, TEST_VALUE_2);
        result.put(PROP5_QNAME_MANDATORY, TEST_VALUE_3);
        result.put(ContentModel.PROP_CONTENT, CONTENT_DATA_TEXT);
        return result;
    }
    
    /**
     * Creates the test model used by the tests
     */
    private void createTestModel()
    {
        M2Model model = M2Model.createModel("test:nodeoperations");
        model.createNamespace(TEST_TYPE_NAMESPACE, "test");
        model.createImport(NamespaceService.DICTIONARY_MODEL_1_0_URI, NamespaceService.DICTIONARY_MODEL_PREFIX);
        model.createImport(NamespaceService.SYSTEM_MODEL_1_0_URI, NamespaceService.SYSTEM_MODEL_PREFIX);
        model.createImport(NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.CONTENT_MODEL_PREFIX);

        M2Type testType = model.createType("test:" + TEST_TYPE_QNAME.getLocalName());
        testType.setParentName("cm:" + ContentModel.TYPE_CONTENT.getLocalName());
        
        M2Property prop1 = testType.createProperty("test:" + PROP1_QNAME_MANDATORY.getLocalName());
        prop1.setMandatory(true);
        prop1.setType("d:" + DataTypeDefinition.TEXT.getLocalName());
        prop1.setMultiValued(false);
        
        M2Property prop2 = testType.createProperty("test:" + PROP2_QNAME_OPTIONAL.getLocalName());
        prop2.setMandatory(false);
        prop2.setType("d:" + DataTypeDefinition.TEXT.getLocalName());
        prop2.setMandatory(false);
        
        M2Property propNodeRef = testType.createProperty("test:" + PROP_QNAME_MY_NODE_REF.getLocalName());
        propNodeRef.setMandatory(false);
        propNodeRef.setType("d:" + DataTypeDefinition.NODE_REF.getLocalName());
        propNodeRef.setMandatory(false);
        
        M2Property propAnyNodeRef = testType.createProperty("test:" + PROP_QNAME_MY_ANY.getLocalName());
        propAnyNodeRef.setMandatory(false);
        propAnyNodeRef.setType("d:" + DataTypeDefinition.ANY.getLocalName());
        propAnyNodeRef.setMandatory(false);
        
        M2ChildAssociation childAssoc = testType.createChildAssociation("test:" + TEST_CHILD_ASSOC_TYPE_QNAME.getLocalName());
        childAssoc.setTargetClassName("sys:base");
        childAssoc.setTargetMandatory(false);
        
        M2Association assoc = testType.createAssociation("test:" + TEST_ASSOC_TYPE_QNAME.getLocalName());
        assoc.setTargetClassName("sys:base");
        assoc.setTargetMandatory(false);
        
        M2Aspect testAspect = model.createAspect("test:" + TEST_ASPECT_QNAME.getLocalName());
        
        M2Property prop3 = testAspect.createProperty("test:" + PROP3_QNAME_MANDATORY.getLocalName());
        prop3.setMandatory(true);
        prop3.setType("d:" + DataTypeDefinition.TEXT.getLocalName());
        prop3.setMultiValued(false);
        
        M2Property prop4 = testAspect.createProperty("test:" + PROP4_QNAME_OPTIONAL.getLocalName());
        prop4.setMandatory(false);
        prop4.setType("d:" + DataTypeDefinition.TEXT.getLocalName());
        prop4.setMultiValued(false);

        M2Aspect testMandatoryAspect = model.createAspect("test:" + TEST_MANDATORY_ASPECT_QNAME.getLocalName());
        M2Property prop5 = testMandatoryAspect.createProperty("test:" + PROP5_QNAME_MANDATORY.getLocalName());
        prop5.setType("d:" + DataTypeDefinition.TEXT.getLocalName());
        prop5.setMandatory(true);

        testType.addMandatoryAspect("test:" + TEST_MANDATORY_ASPECT_QNAME.getLocalName());
        
        dictionaryDAO.putModel(model);
    }
    
    public void testCopyToNewNodeWithPermissions()
    {
        permissionService.setPermission(sourceNodeRef, "Test", PermissionService.READ_PERMISSIONS, true);
        permissionService.setPermission(rootNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.READ, true);
        permissionService.setPermission(rootNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.CREATE_CHILDREN, true);
        assertEquals(3, permissionService.getAllSetPermissions(sourceNodeRef).size());
        
        NodeRef copy = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}aclCopyOne"));
        
        assertEquals(3, permissionService.getAllSetPermissions(copy).size());
       
        // Admin
        
        authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName());

        copy = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}aclCopyTwo"));

        assertEquals(3, permissionService.getAllSetPermissions(copy).size());

        // guest

        authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName());

        copy = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}aclCopyThree"));
        assertEquals(2, permissionService.getAllSetPermissions(copy).size());

        // guest with read permissions - write from ownership
        
        authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName());
        permissionService.setPermission(sourceNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.READ_PERMISSIONS, true);
        authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName());

        copy = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}aclCopyFour"));

        assertEquals(4, permissionService.getAllSetPermissions(copy).size());

        // guest with read and write

        authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName());
        permissionService.setPermission(rootNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.CHANGE_PERMISSIONS, true);
        authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName());

        copy = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}aclCopyFour"));

        assertEquals(5, permissionService.getAllSetPermissions(copy).size());
        
        // guest with write but not read
                      
        authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName());
        permissionService.setPermission(sourceNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.READ_PERMISSIONS, false);
        authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName());

        copy = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}aclCopyFour"));

        assertEquals(3, permissionService.getAllSetPermissions(copy).size());

        authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName());
        permissionService.deletePermission(sourceNodeRef, AuthenticationUtil.getGuestUserName(), PermissionService.READ_PERMISSIONS);
        authenticationComponent.setCurrentUser(AuthenticationUtil.getGuestUserName());

        copy = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}aclCopyFour"));

        assertEquals(3, permissionService.getAllSetPermissions(copy).size());

        
    }
    
    /**
     * Test copy new node within store     
     */
    public void testCopyToNewNode()
    {
        PagingRequest pageRequest = new PagingRequest(10);
        PagingResults<CopyInfo> copies = null;
        
        // Check that the node has no copies
        copies = copyService.getCopies(sourceNodeRef, pageRequest);
        assertEquals("Incorrect number of copies", 0, copies.getPage().size());
        
        // Copy to new node without copying children
        NodeRef copy = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}copyAssoc"));        
        checkCopiedNode(sourceNodeRef, copy, true, true, false);        
        copies = copyService.getCopies(sourceNodeRef, pageRequest);
        assertEquals("Incorrect number of copies", 1, copies.getPage().size());
        
        // Copy to new node, copying children
        NodeRef copy2 = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}copyAssoc2"),
                true);
        checkCopiedNode(sourceNodeRef, copy2, true, true, true);
        copies = copyService.getCopies(sourceNodeRef, pageRequest);
        assertEquals("Incorrect number of copies", 2, copies.getPage().size());
        
        // Check that a copy of a copy works correctly
        NodeRef copyOfCopy = copyService.copy(
                copy,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}copyOfCopy"));
        checkCopiedNode(copy, copyOfCopy, true, true, false);
        
        // TODO check copying from a versioned copy
        // TODO check copying from a lockable copy
        
        // Check copying from a node with content    
        ContentWriter contentWriter = contentService.getWriter(sourceNodeRef, ContentModel.PROP_CONTENT, true);
        contentWriter.putContent(SOME_CONTENT);        
        NodeRef copyWithContent = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}copyWithContent"));
        checkCopiedNode(sourceNodeRef, copyWithContent, true, true, false);
        ContentReader contentReader = contentService.getReader(copyWithContent, ContentModel.PROP_CONTENT);
        assertNotNull(contentReader);
        assertEquals(SOME_CONTENT, contentReader.getContentString());
        
        // TODO check copying to a different store
        
        //System.out.println(
        //        NodeStoreInspector.dumpNodeStore(nodeService, storeRef));
    }
    
    public void testCopiedFromAspect()
    {
        IntegrityChecker integrityChecker = (IntegrityChecker) ctx.getBean("integrityChecker");

        // Create the node used for copying
        ChildAssociationRef childAssocRef = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}test"),
                TEST_TYPE_QNAME,
                createTypePropertyBag());
        NodeRef nodeRef = childAssocRef.getChildRef();

        PagingRequest pageRequest = new PagingRequest(10);
        pageRequest.setRequestTotalCountMax(200);
        PagingResults<CopyInfo> copies = null;
        
        NodeRef firstCopy = null;
        NodeRef secondCopy = null;

        for (int i = 1; i <= 100; i++)
        {
            NodeRef copyNodeRef = copyService.copy(
                    nodeRef,
                    rootNodeRef,
                    ContentModel.ASSOC_CHILDREN,
                    QName.createQName("{test}copyAssoc"));
            if (firstCopy == null)
            {
                firstCopy = copyNodeRef;
            }
            else if (secondCopy == null)
            {
                secondCopy = copyNodeRef;
            }
            copies = copyService.getCopies(nodeRef, pageRequest);
            assertEquals("Total count not correct", new Pair<Integer, Integer>(i, i), copies.getTotalResultCount());
            assertEquals("Incorrect number of copies", (i > 10 ? 10 : i), copies.getPage().size());
            
            // Since the results are paged, make sure that we have the correct results while we only have a page
            boolean found = (i > 10) ? true : false;
            for (CopyInfo copy : copies.getPage())
            {
                if (found)          // Might not be checking if we are over a page
                {
                    break;
                }
                if (copy.getNodeRef().equals(copyNodeRef))
                {
                    found = true;
                }
            }
            assertTrue("Did not find the copy in the list of copies.", found);
            
            // Run integrity checks to ensure that commit has a chance
            integrityChecker.checkIntegrity();
            
            // Now query for copies in current parent location
            copies = copyService.getCopies(nodeRef, rootNodeRef, pageRequest);
            assertEquals("Total count not correct", new Pair<Integer, Integer>(i, i), copies.getTotalResultCount());
            assertEquals("Incorrect number of copies", (i > 10 ? 10 : i), copies.getPage().size());
            
            // Check that the original node can be retrieved
            NodeRef originalCheck = copyService.getOriginal(copyNodeRef);
            assertEquals("Original is not as expected. ", nodeRef, originalCheck);

            // Check that the parent node can be included
            copies = copyService.getCopies(nodeRef, rootNodeRef, pageRequest);
            assertEquals("Total count not correct", new Pair<Integer, Integer>(i, i), copies.getTotalResultCount());
            assertEquals("Incorrect number of copies", (i > 10 ? 10 : i), copies.getPage().size());
            
            // And query against some other parent node
            copies = copyService.getCopies(nodeRef, sourceNodeRef, pageRequest);        // Some arbitrary parent
            assertEquals("Expected to find no copies", 0, copies.getPage().size());
        }
        
        // Should be able to delete the original
        nodeService.deleteNode(nodeRef);
        // Run integrity checks to ensure that commit has a chance
        integrityChecker.checkIntegrity();
        // Should be no original
        NodeRef originalCheck = copyService.getOriginal(firstCopy);
        assertNull("Original should not be present. ", originalCheck);
        assertFalse("Copy should not have cm:copiedfrom aspect. ", nodeService.hasAspect(firstCopy, ContentModel.ASPECT_COPIEDFROM));
    }
    
    /**
     * <a href="https://issues.alfresco.com/jira/browse/MNT-9580">
     *      MNT-9580: Daisy chained cm:original associations are cascade-deleted when the first original is deleted
     * </a>
     */
    public void testCopyOfCopyOfCopy()
    {
        IntegrityChecker integrityChecker = (IntegrityChecker) ctx.getBean("integrityChecker");

        // Create the node used for copying
        ChildAssociationRef childAssocRef = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}test"),
                TEST_TYPE_QNAME,
                createTypePropertyBag());
        NodeRef nodeRef = childAssocRef.getChildRef();

        PagingRequest pageRequest = new PagingRequest(10);
        pageRequest.setRequestTotalCountMax(200);
        PagingResults<CopyInfo> copies = null;
        
        NodeRef currentOriginal = nodeRef;
        NodeRef copyNodeRef = null;

        for (int i = 1; i <= 5; i++)
        {
            copyNodeRef = copyService.copy(
                    currentOriginal,
                    rootNodeRef,
                    ContentModel.ASSOC_CHILDREN,
                    QName.createQName("{test}copyAssoc"+i));
            copies = copyService.getCopies(currentOriginal, pageRequest);
            assertEquals("Incorrect number of copies on iteration " + i, 1, copies.getPage().size());

            // Check that the original node can be retrieved
            NodeRef originalCheck = copyService.getOriginal(copyNodeRef);
            assertEquals("Original is not as expected. ", currentOriginal, originalCheck);
            // Run integrity checks to ensure that commit has a chance
            integrityChecker.checkIntegrity();
            
            currentOriginal = copyNodeRef;
        }
        
        // Now, delete the nodes starting with the first original
        currentOriginal = nodeRef;
        copyNodeRef = null;
        for (int i = 1; i < 5; i++)
        {
            // Each node must be an original
            copies = copyService.getCopies(currentOriginal, pageRequest);
            assertEquals("Incorrect number of copies on iteration " + i, 1, copies.getPage().size());
            copyNodeRef = copies.getPage().get(0).getNodeRef();
            // Delete current original
            nodeService.deleteNode(currentOriginal);
            // Run integrity checks to ensure that commit has a chance
            integrityChecker.checkIntegrity();
            
            currentOriginal = copyNodeRef;
        }
    }
    
    /**
     * Test the behaviour of the aspect when copying types not derived from <b>cm:object</b>
     */
    public void testCopiedFromAspect_NonObject()
    {
        // Create the node used for copying
        ChildAssociationRef childAssocRef = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}test"),
                ContentModel.TYPE_BASE,
                createTypePropertyBag());
        NodeRef nodeRef = childAssocRef.getChildRef();
        // If we copy this, there should not be a cm:source association
        NodeRef copyNodeRef = copyService.copy(
                nodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}copyAssoc"));
        
        assertFalse(
                "cm:copiedfrom should not be present",
                nodeService.hasAspect(copyNodeRef, ContentModel.ASPECT_COPIEDFROM));
    }
    
    public void testCopyNodeWithRules()
    {
        // Create a new rule and add it to the source noderef
        Rule rule = new Rule();
        rule.setRuleType(RuleType.INBOUND);
        
        Map<String, Serializable> props = new HashMap<String, Serializable>(1);
        props.put(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE);
        Action action = actionService.createAction(AddFeaturesActionExecuter.NAME, props);
        rule.setAction(action);
        
        ActionCondition actionCondition = actionService.createActionCondition(NoConditionEvaluator.NAME);
        action.addActionCondition(actionCondition);
        
        ruleService.saveRule(sourceNodeRef, rule);
        assertNotNull(rule.getNodeRef());
        assertEquals(sourceNodeRef, ruleService.getOwningNodeRef(rule));
        
        //System.out.println(
        //        NodeStoreInspector.dumpNodeStore(nodeService, storeRef));
        //System.out.println(" ------------------------------ ");
        
        // Now copy the node that has rules associated with it
        NodeRef copy = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}withRulesCopy"),
                true);
        
        //System.out.println(
         //          NodeStoreInspector.dumpNodeStore(nodeService, storeRef));
        
        checkCopiedNode(sourceNodeRef, copy, true, true, true);   
        
        assertTrue(nodeService.hasAspect(copy, RuleModel.ASPECT_RULES));
        assertTrue(ruleService.hasRules(copy));
        assertTrue(ruleService.rulesEnabled(copy));
        
        List<Rule> copiedRules = ruleService.getRules(copy);
        assertEquals(1, copiedRules.size());
        Rule copiedRule = copiedRules.get(0);
        
        assertNotNull(copiedRule.getNodeRef());
        assertFalse(copiedRule.getNodeRef().equals(rule.getNodeRef()));
        assertEquals(rule.getTitle(), copiedRule.getTitle());
        assertEquals(rule.getDescription(), copiedRule.getDescription());
        assertEquals(copy, ruleService.getOwningNodeRef(copiedRule));
        assertEquals(rule.getAction().getActionDefinitionName(), copiedRule.getAction().getActionDefinitionName());
        
        // Now copy the node without copying the children and check that the rules have been copied
        NodeRef copy2 = copyService.copy(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}withRuleCopyNoChildren"),
                false);
        
//      System.out.println(
        //         NodeStoreInspector.dumpNodeStore(nodeService, storeRef));        
        
        checkCopiedNode(sourceNodeRef, copy2, true, true, false);
        
        //assertTrue(configurableService.isConfigurable(copy2));
        //assertNotNull(configurableService.getConfigurationFolder(copy2));
        //assertFalse(configurableService.getConfigurationFolder(sourceNodeRef) == configurableService.getConfigurationFolder(copy2));
        
        assertTrue(nodeService.hasAspect(copy2, RuleModel.ASPECT_RULES));
        assertTrue(ruleService.hasRules(copy2));
        assertTrue(ruleService.rulesEnabled(copy2));
        List<Rule> copiedRules2 = ruleService.getRules(copy2);
        assertEquals(1, copiedRules.size());
        Rule copiedRule2 = copiedRules2.get(0);
        assertFalse(rule.getNodeRef().equals(copiedRule2.getNodeRef()));
        assertEquals(rule.getTitle(), copiedRule2.getTitle());
        assertEquals(rule.getDescription(), copiedRule2.getDescription());
        assertEquals(ruleService.getOwningNodeRef(copiedRule2), copy2);
        assertEquals(rule.getAction().getActionDefinitionName(), copiedRule2.getAction().getActionDefinitionName());                                
    }
    
    public void testCopyToExistingNode()
    {
        // Copy nodes within the same store
        copyService.copy(sourceNodeRef, destinationNodeRef);
        checkCopiedNode(sourceNodeRef, destinationNodeRef, false, true, true);
        
        // TODO check copying from a copy
        // TODO check copying from a versioned copy
        // TODO check copying from a lockable copy
        // TODO check copying from a node with content
        
        // TODO check copying nodes between stores
        
        //System.out.println(
        //        NodeStoreInspector.dumpNodeStore(nodeService, storeRef));
    }
    
    /**
     * Test a potentially recursive copy
     */
    public void testRecursiveCopy()
    {
        PropertyMap props = new PropertyMap();
        // Need to create a potentially recursive node structure
        props.put(ContentModel.PROP_NODE_UUID, "nodeOne");
        NodeRef nodeOne = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.TYPE_CONTAINER,
                props).getChildRef();
        props.put(ContentModel.PROP_NODE_UUID, "nodeTwo");
        NodeRef nodeTwo = nodeService.createNode(
                nodeOne,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.TYPE_CONTAINER,
                props).getChildRef();
        props.put(ContentModel.PROP_NODE_UUID, "nodeThree");
        NodeRef nodeThree = nodeService.createNode(
                nodeTwo,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.TYPE_CONTAINER,
                props).getChildRef();
        
        // Issue a potentialy recursive copy
        copyService.copy(nodeOne, nodeThree, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, true);
        
        //System.out.println(
        //         NodeStoreInspector.dumpNodeStore(nodeService, storeRef));
    }
    
    /**
     * Tests copying a folder that contains both a node and a copy of that node.
     */
    public void testALF11964_part1()
    {
        IntegrityChecker integrityChecker = (IntegrityChecker) ctx.getBean("integrityChecker");
        
        PropertyMap props = new PropertyMap();
        // Need to create a potentially recursive node structure
        props.put(ContentModel.PROP_NODE_UUID, "nodeOne");
        NodeRef nodeOne = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.TYPE_CONTAINER,
                props).getChildRef();
        props.put(ContentModel.PROP_NODE_UUID, "nodeTwo");
        NodeRef nodeTwo = nodeService.createNode(
                nodeOne,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.TYPE_CONTENT,
                props).getChildRef();
        props.put(ContentModel.PROP_NODE_UUID, "nodeThree");
        NodeRef nodeThree = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.TYPE_CONTAINER,
                props).getChildRef();
        
        copyService.copy(nodeTwo, nodeOne, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, true);

        copyService.copy(nodeOne, nodeThree, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, true);
        
        integrityChecker.checkIntegrity();
    }
    
    /**
     * Tests copying a folder that contains both a checked-out node and its working copy. 
     */
    public void testALF11964_part2()
    {
        IntegrityChecker integrityChecker = (IntegrityChecker) ctx.getBean("integrityChecker");
        
        PropertyMap props = new PropertyMap();
        // Need to create a potentially recursive node structure
        props.put(ContentModel.PROP_NODE_UUID, "nodeOne");
        NodeRef nodeOne = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.TYPE_CONTAINER,
                props).getChildRef();
        props.put(ContentModel.PROP_NODE_UUID, "nodeTwo");
        NodeRef nodeTwo = nodeService.createNode(
                nodeOne,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.TYPE_CONTENT,
                props).getChildRef();
        props.put(ContentModel.PROP_NODE_UUID, "nodeThree");
        NodeRef nodeThree = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.ASSOC_CHILDREN,
                ContentModel.TYPE_CONTAINER,
                props).getChildRef();
        
        cociService.checkout(nodeTwo);

        copyService.copy(nodeOne, nodeThree, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, true);
        
        integrityChecker.checkIntegrity();
    }
    
    public void testCopyResidualProperties() throws Exception
    {
        QName nodeOneAssocName = QName.createQName("{test}nodeOne");
        
        NodeRef nodeOne = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                nodeOneAssocName,
                TEST_TYPE_QNAME).getChildRef();
        nodeService.setProperty(nodeOne, PROP_QNAME_RESIDUAL_NODE_REF, nodeOne);
        nodeService.setProperty(nodeOne, PROP_QNAME_RESIDUAL_ANY, nodeOne);
        NodeRef nodeOneCopy = copyService.copy(
                nodeOne,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}copiedNodeOne"));
        // Check the node ref property
        Serializable propNodeRef = nodeService.getProperty(nodeOneCopy, PROP_QNAME_RESIDUAL_NODE_REF);
        assertEquals("Residual d:noderef not copied", nodeOne, propNodeRef);
        
        // Check the any property
        Serializable propAny = nodeService.getProperty(nodeOneCopy, PROP_QNAME_RESIDUAL_ANY);
        assertEquals("Residual d:any not copied", nodeOne, propAny);
        
    }
    
    /**
     * Test that realtive links between nodes are restored once the copy is completed
     */
    public void testRelativeLinks()
    {
        QName nodeOneAssocName = QName.createQName("{test}nodeOne");
        QName nodeTwoAssocName = QName.createQName("{test}nodeTwo");
        QName nodeThreeAssocName = QName.createQName("{test}nodeThree");
        QName nodeFourAssocName = QName.createQName("{test}nodeFour");
        
        NodeRef nodeNotCopied = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                nodeOneAssocName,
                TEST_TYPE_QNAME).getChildRef();
        NodeRef nodeOne = nodeService.createNode(
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                nodeOneAssocName,
                TEST_TYPE_QNAME).getChildRef();
        NodeRef nodeTwo = nodeService.createNode(
                nodeOne,
                TEST_CHILD_ASSOC_TYPE_QNAME,
                nodeTwoAssocName,
                TEST_TYPE_QNAME).getChildRef();
        NodeRef nodeThree = nodeService.createNode(
                nodeTwo,
                TEST_CHILD_ASSOC_TYPE_QNAME,
                nodeThreeAssocName,
                TEST_TYPE_QNAME).getChildRef();
        NodeRef nodeFour = nodeService.createNode(
                nodeOne,
                TEST_CHILD_ASSOC_TYPE_QNAME,
                nodeFourAssocName,
                TEST_TYPE_QNAME).getChildRef();
        nodeService.addChild(nodeFour, nodeThree, TEST_CHILD_ASSOC_TYPE_QNAME, TEST_CHILD_ASSOC_QNAME);
        nodeService.createAssociation(nodeTwo, nodeThree, TEST_ASSOC_TYPE_QNAME);
        nodeService.createAssociation(nodeTwo, nodeNotCopied, TEST_ASSOC_TYPE_QNAME);
        
        // Make node one actionable with a rule to copy nodes into node two
        Map<String, Serializable> params = new HashMap<String, Serializable>(1);
        params.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, nodeTwo);
        Rule rule = new Rule();
        rule.setRuleType(RuleType.INBOUND);        
        Action action = actionService.createAction(CopyActionExecuter.NAME, params);
        ActionCondition condition = actionService.createActionCondition(NoConditionEvaluator.NAME);
        action.addActionCondition(condition);
        rule.setAction(action);
        ruleService.saveRule(nodeOne, rule);
        
        // Do a deep copy
        NodeRef nodeOneCopy = copyService.copy(nodeOne, rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}copiedNodeOne"), true);
        NodeRef nodeTwoCopy = null;
        NodeRef nodeThreeCopy = null;
        NodeRef nodeFourCopy = null;
        
        //System.out.println(
        //        NodeStoreInspector.dumpNodeStore(nodeService, storeRef));
        
        List<ChildAssociationRef> nodeOneCopyChildren = nodeService.getChildAssocs(nodeOneCopy);
        assertNotNull(nodeOneCopyChildren);
        assertEquals(3, nodeOneCopyChildren.size());
        for (ChildAssociationRef nodeOneCopyChild : nodeOneCopyChildren)
        {
            if (nodeOneCopyChild.getQName().equals(nodeTwoAssocName) == true)
            {
                nodeTwoCopy = nodeOneCopyChild.getChildRef();
                                
                List<ChildAssociationRef>  nodeTwoCopyChildren = nodeService.getChildAssocs(nodeTwoCopy);
                assertNotNull(nodeTwoCopyChildren);
                assertEquals(1, nodeTwoCopyChildren.size());
                for (ChildAssociationRef nodeTwoCopyChild : nodeTwoCopyChildren)
                {
                    if (nodeTwoCopyChild.getQName().equals(nodeThreeAssocName) == true)
                    {
                        nodeThreeCopy = nodeTwoCopyChild.getChildRef();
                    }
                }
            }
            else if (nodeOneCopyChild.getQName().equals(nodeFourAssocName) == true)
            {
                nodeFourCopy = nodeOneCopyChild.getChildRef();
            }
        }
        assertNotNull(nodeTwoCopy);
        assertNotNull(nodeThreeCopy);
        assertNotNull(nodeFourCopy);
        
        // Check the non primary child assoc
        List<ChildAssociationRef> children = nodeService.getChildAssocs(
                nodeFourCopy,
                RegexQNamePattern.MATCH_ALL,
                TEST_CHILD_ASSOC_QNAME);
        assertNotNull(children);
        assertEquals(1, children.size());
        ChildAssociationRef child = children.get(0);
        assertEquals(child.getChildRef(), nodeThree);
        
        // Check the target assoc
        List<AssociationRef> assocs = nodeService.getTargetAssocs(nodeTwoCopy, TEST_ASSOC_TYPE_QNAME);
        assertNotNull(assocs);
        assertEquals(2, assocs.size());
        AssociationRef assoc0 = assocs.get(0);
        assertTrue(assoc0.getTargetRef().equals(nodeThreeCopy) || assoc0.getTargetRef().equals(nodeNotCopied));        
        AssociationRef assoc1 = assocs.get(1);
        assertTrue(assoc1.getTargetRef().equals(nodeThreeCopy) || assoc1.getTargetRef().equals(nodeNotCopied));        
        
        // Check that the rule parameter values have been made relative
        List<Rule> rules = ruleService.getRules(nodeOneCopy);
        assertNotNull(rules);
        assertEquals(1, rules.size());
        Rule copiedRule = rules.get(0);
        assertNotNull(copiedRule);
        Action ruleAction = copiedRule.getAction();
        assertNotNull(ruleAction);
        NodeRef value = (NodeRef)ruleAction.getParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER);
        assertNotNull(value);
        assertEquals(nodeTwoCopy, value);
    }
     
    public void testCopyAndRename()
    {
        // Check a normal copy with no dup restrictions
        NodeRef copy = copyService.copyAndRename(
                sourceNodeRef,
                rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}copyAssoc"),
                false);        
        checkCopiedNode(sourceNodeRef, copy, true, true, false);         
        assertTrue(TEST_NAME.equals(nodeService.getProperty(copy, ContentModel.PROP_NAME)));
        
        // Create a folder and content node        
        Map<QName, Serializable> propsFolder = new HashMap<QName, Serializable>(1);
        propsFolder.put(ContentModel.PROP_NAME, "tempFolder");
        NodeRef folderNode = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef();        
        Map<QName, Serializable> props = new HashMap<QName, Serializable>(1);
        props.put(ContentModel.PROP_NAME, TEST_NAME);
        NodeRef contentNode = nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}renametest"), ContentModel.TYPE_CONTENT, props).getChildRef();
        
        // Now copy the content node with the duplicate name restriction
        NodeRef contentCopy = copyService.copy(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}bobbins"), false);
        assertFalse(TEST_NAME.equals(nodeService.getProperty(contentCopy, ContentModel.PROP_NAME)));
    }
    
    /**
     * https://issues.alfresco.com/jira/browse/ETWOONE-224
     */
    public void testETWOONE_244()
    {
        // Create a folder and content node        
        Map<QName, Serializable> propsFolder = new HashMap<QName, Serializable>(1);
        propsFolder.put(ContentModel.PROP_NAME, "tempFolder");
        NodeRef folderNode = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef();        
        Map<QName, Serializable> props = new HashMap<QName, Serializable>(1);
        props.put(ContentModel.PROP_NAME, "myDoc.txt");
        NodeRef contentNode = nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,  "myDoc.txt"), ContentModel.TYPE_CONTENT, props).getChildRef();
        
        NodeRef copy = copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false);
        assertEquals("Copy of myDoc.txt", nodeService.getProperty(copy, ContentModel.PROP_NAME));
        QName copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of myDoc.txt");
        assertEquals(copyQName, nodeService.getPrimaryParent(copy).getQName());
        
        copy = copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false);
        assertEquals("Copy of Copy of myDoc.txt", nodeService.getProperty(copy, ContentModel.PROP_NAME));
        copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of Copy of myDoc.txt");
        assertEquals(copyQName, nodeService.getPrimaryParent(copy).getQName());        

        copy = copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false);
        assertEquals("Copy of Copy of Copy of myDoc.txt", nodeService.getProperty(copy, ContentModel.PROP_NAME));
        copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of Copy of Copy of myDoc.txt");
        assertEquals(copyQName, nodeService.getPrimaryParent(copy).getQName());
    }
    
    
    /**
     * https://issues.alfresco.com/jira/browse/ALF-3119
     * 
     * Test copying of MLText values.
     */
    public void testCopyMLText()
    {
        // Create a folder and content node        
        Map<QName, Serializable> propsFolder = new HashMap<QName, Serializable>(1);
        propsFolder.put(ContentModel.PROP_NAME, "tempFolder");
        NodeRef folderNode = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "tempFolder"), ContentModel.TYPE_FOLDER, propsFolder).getChildRef();        
        
        Map<QName, Serializable> props = new HashMap<QName, Serializable>(1);
        props.put(ContentModel.PROP_NAME, "myDoc.txt");
        
        String FRENCH_DESCRIPTION = "french description";
        String GERMAN_DESCRIPTION = "german description";
        String ITALY_DESCRIPTION = "italy description";
        String DEFAULT_DESCRIPTION = "default description";
        MLText description = new MLText();
        description.addValue(Locale.getDefault(), DEFAULT_DESCRIPTION);
        description.addValue(Locale.FRANCE, FRENCH_DESCRIPTION);
        description.addValue(Locale.GERMAN, GERMAN_DESCRIPTION);
        description.addValue(Locale.ITALY, ITALY_DESCRIPTION);
        props.put(ContentModel.PROP_DESCRIPTION, description);
        
        NodeRef contentNode = nodeService.createNode(folderNode, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,  "myDoc.txt"), ContentModel.TYPE_CONTENT, props).getChildRef();
        
        NodeRef copy = copyService.copyAndRename(contentNode, folderNode, ContentModel.ASSOC_CONTAINS, null, false);
        assertEquals("Copy of myDoc.txt", nodeService.getProperty(copy, ContentModel.PROP_NAME));
        QName copyQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "Copy of myDoc.txt");
        assertEquals(copyQName, nodeService.getPrimaryParent(copy).getQName());

        // Test uses DB Node Service.
        Serializable desc = nodeService.getProperty(copy, ContentModel.PROP_DESCRIPTION);
        if(desc instanceof MLText)
        {
            // Using a node service without a MLProperty interceptor
            MLText value = (MLText)desc;
            assertEquals("French description is wrong", FRENCH_DESCRIPTION, value.get(Locale.FRANCE));
            assertEquals("German description is wrong", GERMAN_DESCRIPTION, value.get(Locale.GERMAN));               
        }
        else
        {    
          I18NUtil.setLocale(Locale.FRANCE);
          assertEquals("French description is wrong", FRENCH_DESCRIPTION, nodeService.getProperty(copy, ContentModel.PROP_DESCRIPTION));
       
          I18NUtil.setLocale(Locale.GERMAN);
          assertEquals("German description is wrong", GERMAN_DESCRIPTION, nodeService.getProperty(copy, ContentModel.PROP_DESCRIPTION));   
        }
   }

   /**
    * Creates some content as one user, then as another checks:
    *  * If you don't have read permissions to the source you can't copy
    *  * If you don't have write permissions to the target you can't copy
    *  * If you do, you can copy just fine
    */
   public void testCopyUserPermissions() throws Exception
   {
       String nodeTitle = "Test Title String";
       
       // Create a node under the source
       permissionService.setPermission(sourceNodeRef, USER_1, PermissionService.EDITOR, true);
       permissionService.setPermission(targetNodeRef, USER_1, PermissionService.CONTRIBUTOR, true);
       permissionService.setPermission(targetNodeRef, USER_2, PermissionService.CONTRIBUTOR, true);
       
       AuthenticationUtil.setFullyAuthenticatedUser(USER_1);
       NodeRef toCopy = nodeService.createNode(
               sourceNodeRef, ContentModel.ASSOC_CONTAINS, 
               QName.createQName("content"), ContentModel.TYPE_CONTENT).getChildRef();
       nodeService.setProperty(toCopy, ContentModel.PROP_TITLE, nodeTitle);
       
       // Check we can't copy it
       AuthenticationUtil.setFullyAuthenticatedUser(USER_2);
       try {
           copyService.copy(toCopy, targetNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("NewCopy"));
       } catch(AccessDeniedException e) {}
       
       // Allow the read, but the destination won't accept it
       authenticationComponent.setSystemUserAsCurrentUser();
       permissionService.setPermission(sourceNodeRef, USER_2, PermissionService.CONTRIBUTOR, true);
       permissionService.setPermission(targetNodeRef, USER_2, PermissionService.CONTRIBUTOR, false);
       AuthenticationUtil.setFullyAuthenticatedUser(USER_2);
       
       try {
           copyService.copy(toCopy, targetNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("NewCopy"));
       } catch(AccessDeniedException e) {}
       
       
       // Now allow on the destination, should go through
       authenticationComponent.setSystemUserAsCurrentUser();
       permissionService.setPermission(targetNodeRef, USER_2, PermissionService.CONTRIBUTOR, true);
       AuthenticationUtil.setFullyAuthenticatedUser(USER_2);
       
       NodeRef copied = copyService.copy(toCopy, targetNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("NewCopy"));
       
       // Check it got there
       assertEquals(true, nodeService.exists(copied));
       assertEquals(
               nodeTitle, 
               ((MLText)nodeService.getProperty(copied, ContentModel.PROP_TITLE)).getDefaultValue()
       );
       
       
       // Check the owners
       // (The new node should be owned by the person who did the copy)
       assertEquals(USER_1, nodeService.getProperty(toCopy, ContentModel.PROP_CREATOR));
       assertEquals(USER_1, nodeService.getProperty(toCopy, ContentModel.PROP_MODIFIER));
       assertEquals(USER_2, nodeService.getProperty(copied, ContentModel.PROP_CREATOR));
       assertEquals(USER_2, nodeService.getProperty(copied, ContentModel.PROP_MODIFIER));
       
       
       // Check the permissions on the source and target
       
       // On the source, 1 is editor, 2 is contributor
       Set<AccessPermission> perms = permissionService.getAllSetPermissions(toCopy);
       boolean done1 = false;
       boolean done2 = false;
       for(AccessPermission perm : perms)
       {
           if(perm.getAuthority().equals(USER_1))
           {
               done1 = true;
               assertEquals(PermissionService.EDITOR, perm.getPermission());
           }
           if(perm.getAuthority().equals(USER_2))
           {
               done2 = true;
               assertEquals(PermissionService.CONTRIBUTOR, perm.getPermission());
           }
       }
       assertEquals(true, done1);
       assertEquals(true, done2);
       
       // On the target, will have inherited from the folder, so both are contributors
       perms = permissionService.getAllSetPermissions(copied);
       done1 = false;
       done2 = false;
       for(AccessPermission perm : perms)
       {
           if(perm.getAuthority().equals(USER_1))
           {
               done1 = true;
               assertEquals(PermissionService.CONTRIBUTOR, perm.getPermission());
           }
           if(perm.getAuthority().equals(USER_2))
           {
               done2 = true;
               assertEquals(PermissionService.CONTRIBUTOR, perm.getPermission());
           }
       }       
       assertEquals(true, done1);
       assertEquals(true, done2);

       
       // User 2 should be able to edit the new node
       // User 1 should be able to edit the old node
       // They shouldn't be allowed to edit each others
       String titleToFailToSet = "Set Title";
       String description = "Set Description";
       
       AuthenticationUtil.setFullyAuthenticatedUser(USER_1);
       try {
           publicNodeService.setProperty(copied, ContentModel.PROP_TITLE, titleToFailToSet);
           fail("User 1 should no longer have write permissions");
       } catch(AccessDeniedException e) {}
       
       AuthenticationUtil.setFullyAuthenticatedUser(USER_2);
       publicNodeService.setProperty(copied, ContentModel.PROP_DESCRIPTION, description);
       
       assertEquals(
               nodeTitle, 
               ((MLText)nodeService.getProperty(copied, ContentModel.PROP_TITLE)).getDefaultValue()
       );
       assertEquals(
               description, 
               ((MLText)nodeService.getProperty(copied, ContentModel.PROP_DESCRIPTION)).getDefaultValue()
       );
   }
   
    /**
     * Check that the copied node contains the state we are expecting
     * 
     * @param sourceNodeRef       the source node reference
     * @param destinationNodeRef  the destination node reference
     */
    private void checkCopiedNode(
            NodeRef sourceNodeRef, NodeRef destinationNodeRef,
            boolean newCopy, boolean sameStore, boolean copyChildren)
    {
        if (newCopy == true)
        {
            if (sameStore == true)
            {
                // Check that the copy aspect has been applied to the copy
                boolean hasCopyAspect = nodeService.hasAspect(destinationNodeRef, ContentModel.ASPECT_COPIEDFROM);
                assertTrue("Missing aspect: " + ContentModel.ASPECT_COPIEDFROM, hasCopyAspect);
                List<AssociationRef> assocs = nodeService.getTargetAssocs(destinationNodeRef, ContentModel.ASSOC_ORIGINAL);
                assertEquals("Expectd exactly one reference back to original", 1, assocs.size());
                NodeRef checkSourceNodeRef = assocs.get(0).getTargetRef();
                assertEquals("Copy refers to incorrect original source", sourceNodeRef, checkSourceNodeRef);
            }
            else
            {
                // Check that destiantion has the same id as the source
                assertEquals(sourceNodeRef.getId(), destinationNodeRef.getId());
            }
        }
        
        boolean hasTestAspect = nodeService.hasAspect(destinationNodeRef, TEST_ASPECT_QNAME);
        assertTrue(hasTestAspect);
        
        // Check that all the correct properties have been copied
        Map<QName, Serializable> destinationProperties = nodeService.getProperties(destinationNodeRef);
        assertNotNull(destinationProperties);
        String value1 = (String)destinationProperties.get(PROP1_QNAME_MANDATORY);
        assertNotNull(value1);
        assertEquals(TEST_VALUE_1, value1);
        String value2 = (String)destinationProperties.get(PROP2_QNAME_OPTIONAL);
        assertNotNull(value2);
        assertEquals(TEST_VALUE_2, value2);
        String value3 = (String)destinationProperties.get(PROP3_QNAME_MANDATORY);
        assertNotNull(value3);
        assertEquals(TEST_VALUE_1, value3);
        String value4 = (String)destinationProperties.get(PROP4_QNAME_OPTIONAL);
        assertNotNull(value4);
        assertEquals(TEST_VALUE_2, value4);
        
        // Check all the target associations have been copied
        List<AssociationRef> destinationTargets = nodeService.getTargetAssocs(destinationNodeRef, TEST_ASSOC_TYPE_QNAME);
        assertNotNull(destinationTargets);
        assertEquals(1, destinationTargets.size());
        AssociationRef nodeAssocRef = destinationTargets.get(0);
        assertNotNull(nodeAssocRef);
        assertEquals(targetNodeRef, nodeAssocRef.getTargetRef());
        
        // Check all the child associations have been copied
        List<ChildAssociationRef> childAssocRefs = nodeService.getChildAssocs(destinationNodeRef);
        assertNotNull(childAssocRefs);
        int expectedSize = copyChildren ? 2 : 0;
        if (nodeService.hasAspect(destinationNodeRef, RuleModel.ASPECT_RULES) == true)
        {
            expectedSize = expectedSize + 1;
        }
        
        assertEquals(expectedSize, childAssocRefs.size());
        for (ChildAssociationRef ref : childAssocRefs) 
        {
            if (ref.getQName().equals(TEST_CHILD_ASSOC_QNAME2) == true)
            {
                // Since this child is non-primary in the source it will always be non-primary in the destination
                assertFalse(ref.isPrimary());
                assertEquals(nonPrimaryChildNodeRef, ref.getChildRef());
            }
            else
            {
                if (copyChildren == false)
                {
                    if (ref.getTypeQName().equals(RuleModel.ASSOC_RULE_FOLDER) == true)
                    {
                        assertTrue(ref.isPrimary());
                        assertTrue(childNodeRef.equals(ref.getChildRef()) == false);
                    }
                    else
                    {
                        assertFalse(ref.isPrimary());
                        assertEquals(childNodeRef, ref.getChildRef());
                    }
                }
                else
                {
                    assertTrue(ref.isPrimary());
                    assertTrue(childNodeRef.equals(ref.getChildRef()) == false);
                    
                    // TODO need to check that the copied child has all the correct details ..
                }
            }    
        }
    }

    public void testCopyNullPropertyForAlf10712() throws Exception
    {
        nodeService.setType(sourceNodeRef, TYPE_CUSTOM_CMIS_DOCUMENT);
        nodeService.setType(targetNodeRef, TYPE_CUSTOM_CMIS_DOCUMENT);

        Map<QName, Serializable> customProperties = new HashMap<QName, Serializable>();

        customProperties.put(PROP_CUSTOM_STRING, null);
        nodeService.setProperties(sourceNodeRef, customProperties);
        
        Serializable customPropValue = nodeService.getProperty(sourceNodeRef, PROP_CUSTOM_STRING);
        assertNull((PROP_CUSTOM_STRING.toString() + " property must be set to NULL on the source node!"), customPropValue);

        customProperties.put(PROP_CUSTOM_STRING, TEST_VALUE_1);
        nodeService.setProperties(targetNodeRef, customProperties);
        Serializable customProp = nodeService.getProperty(targetNodeRef, PROP_CUSTOM_STRING);
        assertEquals((PROP_CUSTOM_STRING.toString() + " must be set to '" + TEST_VALUE_1 + "' on the target node!"), TEST_VALUE_1, customProp );

        copyService.copy(sourceNodeRef, targetNodeRef);
        Serializable updatedCustomProp = nodeService.getProperty(targetNodeRef, PROP_CUSTOM_STRING);
        assertNull((PROP_CUSTOM_STRING.toString() + " property must be set to NULL on the target node after copying!"), updatedCustomProp );
    }

    /**
     * https://issues.alfresco.com/jira/browse/ALF-17549
     */
    public void testALF17549() throws Exception
    {
        permissionService.setPermission(rootNodeRef, USER_1, PermissionService.COORDINATOR, true);

        AuthenticationUtil.setRunAsUser(USER_1);

        String sourceName = "sourceNode.txt";
        Map<QName, Serializable> props = new HashMap<QName, Serializable>();

        props.put(ContentModel.PROP_NAME, sourceName);

        NodeRef sourceNodeRef = nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}" + sourceName), ContentModel.TYPE_CONTENT, props)
                .getChildRef();

        ContentWriter writer = contentService.getWriter(sourceNodeRef, ContentModel.PROP_CONTENT, true);
        writer.setEncoding("UTF-8");
        writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
        writer.putContent("This is sample text content for unit test.");

        NodeRef targetNodeRef = nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}targetNode"), ContentModel.TYPE_FOLDER)
                .getChildRef();

        List<ChildAssociationRef> childAssoc = nodeService.getChildAssocs(targetNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}sourceNode.html"));

        assertEquals(0, childAssoc.size());

        Action action = this.actionService.createAction(TransformActionExecuter.NAME);

        action.setParameterValue(TransformActionExecuter.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_HTML);
        action.setParameterValue(TransformActionExecuter.PARAM_DESTINATION_FOLDER, targetNodeRef);
        action.setParameterValue(TransformActionExecuter.PARAM_ASSOC_QNAME, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy"));
        action.setParameterValue(TransformActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS);
        actionService.executeAction(action, sourceNodeRef);

        childAssoc = nodeService.getChildAssocs(targetNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName("{test}sourceNode.html"));

        assertEquals(1, childAssoc.size());
    }

    public void testCopyWorkingCopyForAlf8863() throws Exception
    {
        // Test that TopLevelNodeNewName is null for not working copies
        ChildAssociationRef assocRef = nodeService.getPrimaryParent(sourceNodeRef);
        String newNameAfterCopy = copyService.getTopLevelNodeNewName(sourceNodeRef, rootNodeRef, ContentModel.ASSOC_CHILDREN, assocRef.getQName());
        assertNull(newNameAfterCopy);

        // Test that TopLevelNodeNewName is NOT null for working copies
        NodeRef workingCopyRef = cociService.checkout(sourceNodeRef);
        ChildAssociationRef assocWCRef = nodeService.getPrimaryParent(workingCopyRef);
        newNameAfterCopy = copyService.getTopLevelNodeNewName(workingCopyRef, rootNodeRef, ContentModel.ASSOC_CHILDREN, assocWCRef.getQName());
        assertNotNull(newNameAfterCopy);
        assertTrue(newNameAfterCopy.startsWith(TEST_NAME));
        assertFalse(newNameAfterCopy.contains("(Working Copy)"));
        
        // Test copyAndRename call
        NodeRef copyRef = copyService.copyAndRename(workingCopyRef, rootNodeRef, ContentModel.ASSOC_CHILDREN, null, false);
        String copyofWCName = (String)nodeService.getProperty(copyRef, ContentModel.PROP_NAME);
        assertTrue(copyofWCName.startsWith(TEST_NAME));
        assertFalse(copyofWCName.contains("(Working Copy)"));
    }

}