/*
 * #%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.replication;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.transaction.UserTransaction;

import junit.framework.TestCase;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.repo.action.RuntimeActionService;
import org.alfresco.repo.jscript.ClasspathScriptLocation;
import org.alfresco.repo.lock.JobLockService;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.replication.script.ScriptReplicationDefinition;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transfer.TransferServiceImpl2;
import org.alfresco.repo.transfer.TransferTransmitter;
import org.alfresco.repo.transfer.UnitTestInProcessTransmitterImpl;
import org.alfresco.repo.transfer.UnitTestTransferManifestNodeFactory;
import org.alfresco.repo.transfer.manifest.TransferManifestNodeFactory;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.action.ActionStatus;
import org.alfresco.service.cmr.action.ActionTrackingService;
import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedActionService;
import org.alfresco.service.cmr.action.scheduled.SchedulableAction.IntervalPeriod;
import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.lock.UnableToReleaseLockException;
import org.alfresco.service.cmr.replication.DisabledReplicationJobException;
import org.alfresco.service.cmr.replication.ReplicationDefinition;
import org.alfresco.service.cmr.replication.ReplicationService;
import org.alfresco.service.cmr.replication.ReplicationServiceException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.ScriptLocation;
import org.alfresco.service.cmr.repository.ScriptService;
import org.alfresco.service.cmr.transfer.TransferDefinition;
import org.alfresco.service.cmr.transfer.TransferException;
import org.alfresco.service.cmr.transfer.TransferReceiver;
import org.alfresco.service.cmr.transfer.TransferService2;
import org.alfresco.service.cmr.transfer.TransferTarget;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.GUID;
import org.alfresco.util.Pair;
import org.alfresco.util.testing.category.LuceneTests;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.experimental.categories.Category;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * Unit tests for the Replication Service.
 * Handles its own transactions, as in a few cases it needs
 *  to run async actions and know how they'll behave
 * @author Nick Burch
 */
@Category({OwnJVMTestsCategory.class, LuceneTests.class})
public class ReplicationServiceIntegrationTest extends TestCase
{
   private ConfigurableApplicationContext ctx;
   private static final Log log = LogFactory.getLog(ReplicationServiceIntegrationTest.class);
   
   
    private ReplicationActionExecutor replicationActionExecutor;
    private ReplicationService replicationService;
    private ReplicationParams replicationParams;
    private TransactionService transactionService;
    private TransferService2 transferService;
    private ContentService contentService;
    private JobLockService jobLockService;
    private ScriptService scriptService;
    private ActionService actionService;
    private NodeService nodeService;
    private LockService lockService;
    private Repository repositoryHelper;
    private ActionTrackingService actionTrackingService;
    private ScheduledPersistedActionService scheduledPersistedActionService;
    
    private NodeRef replicationRoot;
    
    private NodeRef destinationFolder;
    private NodeRef folder1;
    private NodeRef folder2;
    private NodeRef folder2a;
    private NodeRef folder2b;
    private NodeRef content1_1;
    private NodeRef content1_2;
    private NodeRef thumbnail1_3;  // Thumbnail extends content
    private NodeRef authority1_4;  // Authority doesn't
    private NodeRef content2a_1;
    private NodeRef thumbnail2a_2; // Thumbnail extends content
    private NodeRef zone2a_3;      // Zone doesn't
    private NodeRef deletedFolder;
    
    private final String ACTION_NAME  = "testName";
    private final String ACTION_NAME2 = "testName2";
    private final String ACTION_NAME3 = "testName3";
    private final QName  ACTION_QNAME  = QName.createQName(null, ACTION_NAME);
    private final QName  ACTION_QNAME2 = QName.createQName(null, ACTION_NAME2);
    
    private final String TRANSFER_TARGET = "TestTransferTarget";
    
    @Override
    protected void setUp() throws Exception
    {
        if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE)
        {
            fail("Dangling transaction detected, left by a previous test.");
        }
        ctx = (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext();
        replicationActionExecutor = (ReplicationActionExecutor) ctx.getBean("replicationActionExecutor");
        replicationService = (ReplicationService) ctx.getBean("replicationService");
        replicationParams = (ReplicationParams) ctx.getBean("replicationParams");
        transactionService = (TransactionService) ctx.getBean("transactionService");
        transferService = (TransferService2) ctx.getBean("transferService2");
        contentService = (ContentService) ctx.getBean("contentService");
        jobLockService = (JobLockService) ctx.getBean("jobLockService");
        actionService = (ActionService) ctx.getBean("actionService");
        scriptService = (ScriptService)ctx.getBean("scriptService");
        nodeService = (NodeService) ctx.getBean("NodeService");
        lockService = (LockService) ctx.getBean("lockService");
        repositoryHelper = (Repository) ctx.getBean("repositoryHelper");
        actionTrackingService = (ActionTrackingService) ctx.getBean("actionTrackingService");
        scheduledPersistedActionService = (ScheduledPersistedActionService) ctx.getBean("scheduledPersistedActionService");
        
        // Set the current security context as admin
        AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
        
        replicationParams.setEnabled(true);
        
        UserTransaction txn = transactionService.getUserTransaction();
        txn.begin();
        
        // Zap any existing replication entries
        replicationRoot = ReplicationDefinitionPersisterImpl.REPLICATION_ACTION_ROOT_NODE_REF;
        for(ChildAssociationRef child : nodeService.getChildAssocs(replicationRoot)) {
           QName type = nodeService.getType( child.getChildRef() );
           if(ReplicationDefinitionPersisterImpl.ACTION_TYPES.contains(type)) {
              nodeService.deleteNode(child.getChildRef());
           }
        }
        
        // Create the test folder structure
        destinationFolder = makeNode(repositoryHelper.getCompanyHome(), ContentModel.TYPE_FOLDER, "ReplicationTransferDestination");
        folder1 = makeNode(repositoryHelper.getCompanyHome(), ContentModel.TYPE_FOLDER);
        folder2 = makeNode(repositoryHelper.getCompanyHome(), ContentModel.TYPE_FOLDER);
        folder2a = makeNode(folder2, ContentModel.TYPE_FOLDER);
        folder2b = makeNode(folder2, ContentModel.TYPE_FOLDER);
        
        content1_1 = makeNode(folder1, ContentModel.TYPE_CONTENT);
        content1_2 = makeNode(folder1, ContentModel.TYPE_CONTENT);
        thumbnail1_3 = makeNode(folder1, ContentModel.TYPE_THUMBNAIL);
        authority1_4 = makeNode(folder1, ContentModel.TYPE_AUTHORITY);
        content2a_1 = makeNode(folder2a, ContentModel.TYPE_CONTENT);
        thumbnail2a_2 = makeNode(folder2a, ContentModel.TYPE_THUMBNAIL);
        zone2a_3 = makeNode(folder2a, ContentModel.TYPE_ZONE);
        
        deletedFolder = makeNode(repositoryHelper.getCompanyHome(), ContentModel.TYPE_FOLDER);
        nodeService.deleteNode(deletedFolder);
        
        // Tell the transfer service not to use HTTP
        makeTransferServiceLocal();
        
        // Finish setup
        txn.commit();
    }
    
    @Override
    protected void tearDown() throws Exception
    {
        RetryingTransactionCallback<Void> cleanupCallback = new RetryingTransactionCallback<Void>()
        {
            @Override
            public Void execute() throws Throwable
            {
                // Zap our test folders
                if( folder1 != null)
                {
                    nodeService.deleteNode(folder1);
                }
                if (folder2 != null)
                {
                    nodeService.deleteNode(folder2);
                }
                // Zap the destination folder, which may well contain entries transfered over which are locked
                if (destinationFolder != null)
                {
                   lockService.unlock(destinationFolder, true);
                   nodeService.deleteNode(destinationFolder);
                }
                return null;
            }
        };
        transactionService.getRetryingTransactionHelper().doInTransaction(cleanupCallback);
        
        RetryingTransactionCallback<Void> cleanupTargetCallback = new RetryingTransactionCallback<Void>()
        {
            @Override
            public Void execute() throws Throwable
            {
                // Zap our test transfer target
                transferService.deleteTransferTarget(TRANSFER_TARGET);
                return null;
            }
        };
        try
        {
            transactionService.getRetryingTransactionHelper().doInTransaction(cleanupTargetCallback);
        }
        catch (TransferException e)
        {
            // Ignore
        }
        
        if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE)
        {
            fail("Dangling transaction detected, current test failed to tidy up.");
        }
   }



   public void testCreation() throws Exception
    {
       ReplicationDefinition replicationAction =
          replicationService.createReplicationDefinition(ACTION_NAME, "Test Definition");
       assertNotNull(replicationAction);
       assertEquals("Test Definition", replicationAction.getDescription());
       assertEquals(ACTION_NAME, replicationAction.getReplicationName());
       assertEquals(ACTION_QNAME, replicationAction.getReplicationQName());
       
       String id = replicationAction.getId();
       assertNotNull(id);
       assertTrue(id.length() > 0);
       
       assertNotNull(replicationAction.getPayload());
       assertEquals(0, replicationAction.getPayload().size());
       
       assertNull(replicationAction.getLocalTransferReport());
       assertNull(replicationAction.getRemoteTransferReport());
    }
    
    public void testCreateSaveLoad() throws Exception
    {
       ReplicationDefinition replicationAction =
          replicationService.createReplicationDefinition(ACTION_NAME, "Test Definition");
       String initialId = replicationAction.getId();
       replicationAction.getPayload().add(
             new NodeRef("workspace://SpacesStore/Testing")
       );
       replicationAction.getPayload().add(
             new NodeRef("workspace://SpacesStore/Testing2")
       );
       assertEquals(2, replicationAction.getPayload().size());
       
       replicationService.saveReplicationDefinition(replicationAction);
       
       // Load it again, should have the same details still
       ReplicationDefinition retrieved =
          replicationService.loadReplicationDefinition(ACTION_NAME);
       assertNotNull(retrieved);
       assertEquals(initialId, retrieved.getId());
       assertEquals(ACTION_NAME, retrieved.getReplicationName());
       assertEquals(ACTION_QNAME, retrieved.getReplicationQName());
       assertEquals("Test Definition", retrieved.getDescription());
       assertEquals(2, retrieved.getPayload().size());
       
       // Load a 2nd copy, won't be any changes
       ReplicationDefinition second =
          replicationService.loadReplicationDefinition(ACTION_NAME);
       assertNotNull(second);
       assertEquals(initialId, second.getId());
       assertEquals(ACTION_NAME, second.getReplicationName());
       assertEquals(ACTION_QNAME, second.getReplicationQName());
       assertEquals("Test Definition", second.getDescription());
       assertEquals(2, second.getPayload().size());
    }
    
    public void testLoadList() throws Exception
    {
       assertEquals(0, replicationService.loadReplicationDefinitions().size());

       // Create and store
       ReplicationDefinition rd1 = replicationService.createReplicationDefinition(ACTION_NAME, "Test 1");
       ReplicationDefinition rd2 = replicationService.createReplicationDefinition(ACTION_NAME2, "Test 2");
       
       assertEquals(0, replicationService.loadReplicationDefinitions().size());

       replicationService.saveReplicationDefinition(rd1);
       
       assertEquals(1, replicationService.loadReplicationDefinitions().size());
       assertEquals(ACTION_NAME, replicationService.loadReplicationDefinitions().get(0).getReplicationName());
       
       replicationService.saveReplicationDefinition(rd2);
       assertEquals(2, replicationService.loadReplicationDefinitions().size());
    }
    
    public void testLoadByTarget() throws Exception
    {
       assertEquals(0, replicationService.loadReplicationDefinitions().size());
       assertEquals(0, replicationService.loadReplicationDefinitions("TestTarget").size());
       assertEquals(0, replicationService.loadReplicationDefinitions("TestTarget2").size());
       
       // Store some
       ReplicationDefinition rdTT = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       rdTT.setTargetName("TestTarget");
       replicationService.saveReplicationDefinition(rdTT);
       
       // Check it shows up correctly
       assertEquals(1, replicationService.loadReplicationDefinitions().size());
       assertEquals(1, replicationService.loadReplicationDefinitions("TestTarget").size());
       assertEquals(0, replicationService.loadReplicationDefinitions("TestTarget2").size());
    }
    
    /**
     * Ensures that deletion works correctly
     */
    public void testDeletion() throws Exception
    {
       // Delete does nothing if not persisted
       assertEquals(0, replicationService.loadReplicationDefinitions().size());
       ReplicationDefinition rd1 = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       assertEquals(0, replicationService.loadReplicationDefinitions().size());
       
       replicationService.deleteReplicationDefinition(rd1);
       assertEquals(0, replicationService.loadReplicationDefinitions().size());
       
       
       // Create and save two
       ReplicationDefinition rd2 = replicationService.createReplicationDefinition(ACTION_NAME2, "Test2");
       replicationService.saveReplicationDefinition(rd1);
       replicationService.saveReplicationDefinition(rd2);
       assertEquals(2, replicationService.loadReplicationDefinitions().size());
       
       
       // Delete one - the correct one goes!
       replicationService.deleteReplicationDefinition(rd2);
       assertEquals(1, replicationService.loadReplicationDefinitions().size());
       assertEquals(ACTION_NAME, replicationService.loadReplicationDefinitions().get(0).getReplicationName());
       assertNotNull(replicationService.loadReplicationDefinition(ACTION_NAME));
       assertNull(replicationService.loadReplicationDefinition(ACTION_NAME2));
       
       
       // Re-delete already deleted, no change
       replicationService.deleteReplicationDefinition(rd2);
       assertEquals(1, replicationService.loadReplicationDefinitions().size());
       assertEquals(ACTION_NAME, replicationService.loadReplicationDefinitions().get(0).getReplicationName());
       assertNotNull(replicationService.loadReplicationDefinition(ACTION_NAME));
       assertNull(replicationService.loadReplicationDefinition(ACTION_NAME2));
       
       
       // Delete the 2nd
       replicationService.deleteReplicationDefinition(rd1);
       assertEquals(0, replicationService.loadReplicationDefinitions().size());
       assertNull(replicationService.loadReplicationDefinition(ACTION_NAME));
       assertNull(replicationService.loadReplicationDefinition(ACTION_NAME2));
       
       
       // Can add back in again after being deleted
       replicationService.saveReplicationDefinition(rd1);
       assertEquals(1, replicationService.loadReplicationDefinitions().size());
       assertEquals(ACTION_NAME, replicationService.loadReplicationDefinitions().get(0).getReplicationName());
    }
    
    /**
     * Ensures that we can create, save, edit, save
     *  load, edit, save, load etc, all without
     *  problems, and without creating duplicates
     */
    public void testEditing() throws Exception
    {
       ReplicationDefinition rdTT = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       rdTT.setTargetName("TestTarget");
       replicationService.saveReplicationDefinition(rdTT);
       
       // Load, and check it hasn't changed
       rdTT = replicationService.loadReplicationDefinition(ACTION_NAME);
       assertEquals(ACTION_NAME, rdTT.getReplicationName());
       assertEquals("Test", rdTT.getDescription());
       assertEquals("TestTarget", rdTT.getTargetName());
       assertEquals(true, rdTT.isEnabled());
       assertEquals(0, rdTT.getPayload().size());
       
       // Save and re-load without changes
       replicationService.saveReplicationDefinition(rdTT);
       rdTT = replicationService.loadReplicationDefinition(ACTION_NAME);
       assertEquals(ACTION_NAME, rdTT.getReplicationName());
       assertEquals("Test", rdTT.getDescription());
       assertEquals("TestTarget", rdTT.getTargetName());
       assertEquals(true, rdTT.isEnabled());
       assertEquals(0, rdTT.getPayload().size());
       
       // Make some small changes
       rdTT.setDescription("Test Description");
       rdTT.getPayload().add(folder2a);
       rdTT.setEnabled(false);
       
       // Check we see them on save/load
       replicationService.saveReplicationDefinition(rdTT);
       rdTT = replicationService.loadReplicationDefinition(ACTION_NAME);
       assertEquals(ACTION_NAME, rdTT.getReplicationName());
       assertEquals("Test Description", rdTT.getDescription());
       assertEquals("TestTarget", rdTT.getTargetName());
       assertEquals(false, rdTT.isEnabled());
       assertEquals(1, rdTT.getPayload().size());
       assertEquals(folder2a, rdTT.getPayload().get(0));
       
       // And some more changes
       rdTT.setDescription("Another One");
       rdTT.getPayload().clear();
       rdTT.getPayload().add(folder1);
       rdTT.getPayload().add(folder2b);
       assertEquals(2, rdTT.getPayload().size());
       
       // Ensure these also come with save/load
       replicationService.saveReplicationDefinition(rdTT);
       rdTT = replicationService.loadReplicationDefinition(ACTION_NAME);
       assertEquals(ACTION_NAME, rdTT.getReplicationName());
       assertEquals("Another One", rdTT.getDescription());
       assertEquals("TestTarget", rdTT.getTargetName());
       assertEquals(false, rdTT.isEnabled());
       assertEquals(2, rdTT.getPayload().size());
       assertEquals(folder1, rdTT.getPayload().get(0));
       assertEquals(folder2b, rdTT.getPayload().get(1));
       
       // And more payload changes
       rdTT.getPayload().clear();
       rdTT.getPayload().add(content1_1);
       assertEquals(1, rdTT.getPayload().size());
       rdTT.setEnabled(true);
       
       replicationService.saveReplicationDefinition(rdTT);
       rdTT = replicationService.loadReplicationDefinition(ACTION_NAME);
       assertEquals(ACTION_NAME, rdTT.getReplicationName());
       assertEquals("Another One", rdTT.getDescription());
       assertEquals("TestTarget", rdTT.getTargetName());
       assertEquals(true, rdTT.isEnabled());
       assertEquals(1, rdTT.getPayload().size());
       assertEquals(content1_1, rdTT.getPayload().get(0));
    }
    
    /**
     * Tests that we can rename definitions
     */
    public void testRenaming() throws Exception
    {
       // Create one instance
       ReplicationDefinition rdTT = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       rdTT.setTargetName("TestTarget");
       replicationService.saveReplicationDefinition(rdTT);
       assertEquals(1, replicationService.loadReplicationDefinitions().size());
       
       
       // Rename it
       replicationService.renameReplicationDefinition(ACTION_NAME, ACTION_NAME2);
       
       assertEquals(1, replicationService.loadReplicationDefinitions().size());
       assertEquals(null, replicationService.loadReplicationDefinition(ACTION_NAME));
       
       rdTT = replicationService.loadReplicationDefinition(ACTION_NAME2);
       assertNotNull(rdTT);
       assertEquals(ACTION_NAME2, rdTT.getReplicationName());
       assertEquals(ACTION_QNAME2, rdTT.getReplicationQName());
       
       
       // If the source name doesn't exist, does nothing
       replicationService.renameReplicationDefinition(ACTION_NAME, ACTION_NAME2);
       
       assertEquals(1, replicationService.loadReplicationDefinitions().size());
       assertEquals(null, replicationService.loadReplicationDefinition(ACTION_NAME));
       
       rdTT = replicationService.loadReplicationDefinition(ACTION_NAME2);
       assertNotNull(rdTT);
       assertEquals(ACTION_NAME2, rdTT.getReplicationName());
       assertEquals(ACTION_QNAME2, rdTT.getReplicationQName());

       
       // Renaming to a duplicate name breaks
       rdTT = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       rdTT.setTargetName("TestTarget");
       replicationService.saveReplicationDefinition(rdTT);
       assertEquals(2, replicationService.loadReplicationDefinitions().size());

       try {
          replicationService.renameReplicationDefinition(ACTION_NAME, ACTION_NAME2);
          fail("Shouldn't be able to rename onto a duplicate name");
       } catch(ReplicationServiceException e) {}
    }

    /**
     * Test that the action service can find the executor
     *  for us, and that it has everything it needs
     */
    public void testBasicExecution() throws Exception
    {
       // We need the test transfer target for this test
       makeTransferTarget();
             
       // Ensure the destination is empty 
       // (don't want to get confused with older runs)
       assertEquals(0, nodeService.getChildAssocs(destinationFolder).size());
       
       
       // First one with no target, which isn't allowed
       ReplicationDefinition rd = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       UserTransaction txn = transactionService.getUserTransaction();
       txn.begin();
       try {
          actionService.executeAction(rd, replicationRoot);
          fail("Shouldn't be permitted with no Target defined");
       } catch(ReplicationServiceException e) {}
       txn.rollback();
       
       
       // Now no payload, also not allowed
       rd.setTargetName(TRANSFER_TARGET);
       txn = transactionService.getUserTransaction();
       txn.begin();
       try {
          actionService.executeAction(rd, replicationRoot);
          fail("Shouldn't be permitted with no payload defined");
       } catch(ReplicationServiceException e) {}
       txn.rollback();
       
       
       // Invalid Transfer Target, not allowed
       rd = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       rd.setTargetName("I am an invalid target that isn't there");
       rd.getPayload().add( folder1 );
       txn = transactionService.getUserTransaction();
       txn.begin();
       try {
          actionService.executeAction(rd, replicationRoot);
          fail("Shouldn't be permitted with an invalid transfer target");
       } catch(ReplicationServiceException e) {}
       txn.rollback();
       
       // Can't send Folder2a if Folder2 isn't there, as it
       //  won't have anywhere to put it
       rd = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       rd.setTargetName(TRANSFER_TARGET);
       rd.getPayload().add( folder2a );
       txn = transactionService.getUserTransaction();
       txn.begin();
       try {
          actionService.executeAction(rd, replicationRoot);
          fail("Shouldn't be able to send Folder2a when Folder2 is missing!");
       } catch(ReplicationServiceException e) {}
       txn.rollback();
       
       // Next a proper one with a transient definition,
       //  and a sensible set of folders
       rd = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       rd.setTargetName(TRANSFER_TARGET);
       rd.getPayload().add( folder1 );
       // A deleted folder is fine, will be skipped
       rd.getPayload().add( deletedFolder ); 
       
       // Will execute without error
       txn = transactionService.getUserTransaction();
       txn.begin();
       try {
           actionService.executeAction(rd, replicationRoot);
       } catch(ReplicationServiceException e) {
           // This shouldn't happen normally! Something is wrong!
           // Tidy up before we throw the exception
           txn.rollback();
           throw e;
       }
       txn.commit();
       
       
       // Now with one that's in the repo
       ReplicationDefinition rd2 = replicationService.createReplicationDefinition(ACTION_NAME2, "Test");
       rd2.setTargetName(TRANSFER_TARGET);
       rd2.getPayload().add(
             folder2
       );
       replicationService.saveReplicationDefinition(rd2);
       rd2 = replicationService.loadReplicationDefinition(ACTION_NAME2);
       
       // Again no errors
       txn = transactionService.getUserTransaction();
       txn.begin();
       actionService.executeAction(rd2, replicationRoot);
       txn.commit();
       
       // Now disabled, not allowed
       assertEquals(true, rd.isEnabled());
       rd.setEnabled(false);
       assertEquals(false, rd.isEnabled());
       txn = transactionService.getUserTransaction();
       txn.begin();
       try {
          actionService.executeAction(rd, replicationRoot);
          fail("Shouldn't be permitted when disabled");
       } catch(ReplicationServiceException e) {
           //check if throwed exception is of expected type
           assertTrue(e instanceof DisabledReplicationJobException);
           assertTrue(actionService instanceof RuntimeActionService);
           if (actionService instanceof RuntimeActionService){
               RuntimeActionService runtimeActionService = (RuntimeActionService)actionService;
               //check if throwed exception is considered handled 
               assertTrue(runtimeActionService.onLogException(rd, log, e, e.getMessage()));
           }
         
       }
       txn.rollback();
       rd.setEnabled(true);
       
       
       // Schedule it for 0.5 seconds into the future
       // Ensure that it is run to completion 
       txn = transactionService.getUserTransaction();
       txn.begin();
       
       ((ActionImpl)rd2).setExecutionStatus(ActionStatus.New);
       
       replicationService.enableScheduling(rd2);
       rd2.setScheduleStart(new Date(System.currentTimeMillis()+500));
       replicationService.saveReplicationDefinition(rd2);
       
       txn.commit();
       
       // Wait for it to run
       Thread.sleep(2000);
       for(int i=0; i<100; i++)
       {
          txn = transactionService.getUserTransaction();
          txn.begin();
          rd2 = replicationService.loadReplicationDefinition(ACTION_NAME2);
          txn.commit();
          
          if(rd2.getExecutionStatus().equals(ActionStatus.New) ||
                rd2.getExecutionStatus().equals(ActionStatus.Pending) ||
                rd2.getExecutionStatus().equals(ActionStatus.Running))
          {
             Thread.sleep(50);
          }
       }
       
       // Check it worked
       assertEquals(ActionStatus.Completed, rd2.getExecutionStatus());
    }
    
    /**
     * Check that the locking works.
     * Take a 10 second lock on the job, then execute.
     * Ensure that we really wait a little over 10 seconds.
     */
    public void testReplicationExecutionLocking() throws Exception
    {
       // We need the test transfer target for this test
       makeTransferTarget();

       // Create a task
       ReplicationDefinition rd = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       rd.setTargetName(TRANSFER_TARGET);
       rd.getPayload().add(folder1);
       rd.getPayload().add(folder2);
       
       // Get the lock, and run
       long start = System.currentTimeMillis();
       String token = jobLockService.getLock(
             rd.getReplicationQName(),
             10 * 1000,
             1,
             1
       );
       
       UserTransaction txn = transactionService.getUserTransaction();
       txn.begin();
       try {
           actionService.executeAction(rd, replicationRoot);
       } catch(ReplicationServiceException e) {
           // This shouldn't happen normally! Something is wrong!
           // Tidy up before we throw the exception
           txn.rollback();
           throw e;
       }
       txn.commit();
       long end = System.currentTimeMillis();
       
       assertTrue(
            "Should wait for the lock, but didn't (waited " + 
               ((end-start)/1000.0) + " seconds, not 10)",
            end-start > 10000
       );
    }
    
    /**
     * Check that cancelling works.
     * Does this by taking a lock on the job, cancelling,
     *  releasing and seeing it abort.
     *  
     * Tests that when we ask for a replication task to be cancelled,
     *  that it starts, cancels, and the status is correctly recorded
     *  for it.
     */
    public void testReplicationExecutionCancelling() throws Exception
    {
       // We need the test transfer target for this test
       makeTransferTarget();

       // Create a task
       ReplicationDefinition rd = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       rd.setTargetName(TRANSFER_TARGET);
       rd.getPayload().add(folder1);
       rd.getPayload().add(folder2);
       
       // Get the lock for 2 seconds
       String token = jobLockService.getLock(
             rd.getReplicationQName(),
             2 * 1000,
             1,
             1
       );
       
       // Request it be run async
       UserTransaction txn = transactionService.getUserTransaction();
       txn.begin();
       actionService.executeAction(rd, replicationRoot, false, true);
       assertEquals(ActionStatus.Pending, rd.getExecutionStatus());
       
       assertEquals(false, actionTrackingService.isCancellationRequested(rd));
       actionTrackingService.requestActionCancellation(rd);
       assertEquals(true, actionTrackingService.isCancellationRequested(rd));
       
       txn.commit();

       // Let it get going, will be waiting for the lock
       //  having registered with the action tracking service
       for(int i=0; i<100; i++) {
          // Keep asking for it to be cancelled ASAP
          actionTrackingService.requestActionCancellation(rd);
          
          if(rd.getExecutionStatus().equals(ActionStatus.Running)) {
             // Good, has started up
             // Stop waiting and do the cancel
             break;
          } else {
             // Still pending, wait a bit more
             Thread.sleep(10);
          }
       }

       // Ensure it started, and should shortly stop
       assertEquals(ActionStatus.Running, rd.getExecutionStatus());
       assertEquals(true, actionTrackingService.isCancellationRequested(rd));
       
       // Release our lock, should allow the replication task
       //  to get going and spot the cancel
       jobLockService.releaseLock(token, rd.getReplicationQName());
       
       // Let the main replication task run to cancelled/completed
       // This can take quite some time though...
       for(int i=0; i<10; i++) {
          if(rd.getExecutionStatus() == ActionStatus.Running) {
             Thread.sleep(1000);
          } else {
             // It has finished running, check it
             break;
          }
       }
       
       // Ensure it was cancelled
       assertEquals(null, rd.getExecutionFailureMessage());
       assertNotNull(rd.getLocalTransferReport());
       assertNotNull(rd.getRemoteTransferReport());
       assertEquals(ActionStatus.Cancelled, rd.getExecutionStatus());
    }
    
    /**
     * Test that when we execute a replication task, the
     *  right stuff ends up being moved for us
     */
    public void testExecutionResult() throws Exception
    {
       UserTransaction txn = transactionService.getUserTransaction();
       txn.begin();
       
       // Destination is empty
       assertEquals(0, nodeService.getChildAssocs(destinationFolder).size());
       
       // We need the test transfer target for this test
       makeTransferTarget();

       // Put in Folder 2, so we can send Folder 2a
       String folder2Name = (String)nodeService.getProperties(folder2).get(ContentModel.PROP_NAME);
       NodeRef folderT2 = makeNode(destinationFolder, ContentModel.TYPE_FOLDER, folder2Name);
       txn.commit();
       
       
       // Run a transfer
       ReplicationDefinition rd = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       rd.setTargetName(TRANSFER_TARGET);
       rd.getPayload().add(folder1);
       rd.getPayload().add(folder2a);
       
       assertEquals(null, rd.getLocalTransferReport());
       assertEquals(null, rd.getRemoteTransferReport());
       
       txn = transactionService.getUserTransaction();
       txn.begin();
       try {
           actionService.executeAction(rd, replicationRoot);
       } catch(ReplicationServiceException e) {
           // This shouldn't happen normally! Something is wrong!
           // Tidy up before we throw the exception
           txn.rollback();
           throw e;
       }
       txn.commit();
       
       // Correct things have turned up
       assertEquals(2, nodeService.getChildAssocs(destinationFolder).size());
       NodeRef c1 = nodeService.getChildAssocs(destinationFolder).get(0).getChildRef();
       NodeRef c2 = nodeService.getChildAssocs(destinationFolder).get(1).getChildRef();
       
       // The destination should have folder 1 (transfered) 
       //  and folder 2 (created). folder 2 will have
       //  folder 2a (transfered) but not 2b
       NodeRef folderT1 = null;
       boolean foundT1 = false;
       boolean foundT2 = false;
       if(nodeService.getProperty(folder1, ContentModel.PROP_NAME).equals(
             nodeService.getProperty(c1, ContentModel.PROP_NAME) )) {
          folderT1 = c1;
          foundT1 = true;
       }
       if(nodeService.getProperty(folder1, ContentModel.PROP_NAME).equals(
             nodeService.getProperty(c2, ContentModel.PROP_NAME) )) {
          folderT1 = c2;
          foundT1 = true;
       }
       if(c1.equals(folderT2) || c2.equals(folderT2)) {
          foundT2 = true;
       }

       if(!foundT1) {
          fail("Folder 1 not found in the destination");
       }
       if(!foundT2) {
          fail("Folder 2 not found in the destination");
       }
       
       // Folder 1 has 2*content + thumbnail
       assertEquals(3, nodeService.getChildAssocs(folderT1).size());
       // Won't have the authority, as that gets skipped
       for(ChildAssociationRef r : nodeService.getChildAssocs(folderT1)) {
          if(nodeService.getType(r.getChildRef()).equals(ContentModel.TYPE_AUTHORITY)) {
             fail("Found authority as " + r.getChildRef() + " but it shouldn't be transfered!");
          }
       }
       
       // Folder 2 has 2a but not 2b, since only
       //  2a was transfered
       assertEquals(1, nodeService.getChildAssocs(folderT2).size());
       NodeRef folderT2a = nodeService.getChildAssocs(folderT2).get(0).getChildRef();
       assertEquals(
             nodeService.getProperty(folder2a, ContentModel.PROP_NAME),
             nodeService.getProperty(folderT2a, ContentModel.PROP_NAME)
       );
       // Won't have Folder 2b, as it wasn't on the payload
       for(ChildAssociationRef r : nodeService.getChildAssocs(folderT2)) {
          assertNotSame(
                nodeService.getProperty(folder2b, ContentModel.PROP_NAME),
                nodeService.getProperty(r.getChildRef(), ContentModel.PROP_NAME)
          );
       }
       
       // Folder 2a has content + thumbnail
       assertEquals(2, nodeService.getChildAssocs(folderT2a).size());
       // Won't have the zone, as that gets skipped
       for(ChildAssociationRef r : nodeService.getChildAssocs(folderT2a)) {
          if(nodeService.getType(r.getChildRef()).equals(ContentModel.TYPE_ZONE)) {
             fail("Found zone as " + r.getChildRef() + " but it shouldn't be transfered!");
          }
       }
       
       // Check we got transfer reports, and they look sensible
       NodeRef localReport = rd.getLocalTransferReport();
       assertNotNull(localReport);
       NodeRef remoteReport = rd.getRemoteTransferReport();
       assertNotNull(remoteReport);
       
       txn = transactionService.getUserTransaction();
       txn.begin();
       
       ContentReader localReader = 
          contentService.getReader(localReport, ContentModel.PROP_CONTENT);
       String localReportContent = localReader.getContentString();
       
       assertTrue("XML not found in:\n" + localReportContent, localReportContent.contains("<?xml"));
       assertTrue("Report XML not found in:\n" + localReportContent, localReportContent.contains("<report:transferReport"));

       ContentReader remoteReader = 
           contentService.getReader(remoteReport, ContentModel.PROP_CONTENT);
       String remoteReportContent = remoteReader.getContentString();

       assertTrue("XML not found in:\n" + remoteReportContent, remoteReportContent.contains("<?xml"));
       assertTrue("Report Status not found in:\n" + remoteReportContent, remoteReportContent.contains("state=\"COMPLETE\""));

       txn.commit();
    }
    
    /**
     * Test that we turn a list of payload node starting points
     *  into the correct set of nodes to pass to the 
     *  transfer service.
     */
    public void testReplicationPayloadExpansion() throws Exception
    {
       ReplicationDefinition rd = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       Set<NodeRef> expanded;
       
       // Empty folder -> just itself
       rd.getPayload().clear();
       rd.getPayload().add(folder2b);
       expanded = replicationActionExecutor.expandPayload(rd);
       assertEquals(1, expanded.size());
       assertTrue(expanded.contains(folder2b));
       
       // Folder with content and thumbnails - just content + thumbnail + folder
       rd.getPayload().clear();
       rd.getPayload().add(folder1);
       expanded = replicationActionExecutor.expandPayload(rd);
       assertEquals(4, expanded.size());
       assertTrue(expanded.contains(folder1));
       assertTrue(expanded.contains(content1_1));
       assertTrue(expanded.contains(content1_2));
       assertTrue(expanded.contains(thumbnail1_3));
       assertFalse(expanded.contains(authority1_4)); // Wrong type, won't be there
       
       // Folder with folders - descends properly
       rd.getPayload().clear();
       rd.getPayload().add(folder2);
       expanded = replicationActionExecutor.expandPayload(rd);
       assertEquals(5, expanded.size());
       assertTrue(expanded.contains(folder2));
       assertTrue(expanded.contains(folder2a));
       assertTrue(expanded.contains(content2a_1));
       assertTrue(expanded.contains(thumbnail2a_2));
       assertFalse(expanded.contains(zone2a_3)); // Wrong type, won't be there
       assertTrue(expanded.contains(folder2b));
       
       // Multiple things - gets each in turn
       rd.getPayload().clear();
       rd.getPayload().add(folder1);
       rd.getPayload().add(folder2);
       expanded = replicationActionExecutor.expandPayload(rd);
       assertEquals(9, expanded.size());
       assertTrue(expanded.contains(folder1));
       assertTrue(expanded.contains(content1_1));
       assertTrue(expanded.contains(content1_2));
       assertTrue(expanded.contains(thumbnail1_3));
       assertTrue(expanded.contains(folder2));
       assertTrue(expanded.contains(folder2a));
       assertTrue(expanded.contains(content2a_1));
       assertTrue(expanded.contains(thumbnail2a_2));
       assertTrue(expanded.contains(folder2b));
       
       // TODO Test how options like permissions and renditions
       //  affects what gets sent back
    }

    /**
     * Test that we turn a replication definition correctly
     *  into a transfer definition
     */
    public void testTransferDefinitionBuilding() throws Exception
    {
       ReplicationDefinition rd = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       
       Set<NodeRef> nodes = new HashSet<NodeRef>();
       nodes.add(folder1);
       nodes.add(content1_1);
       
       TransferDefinition td = replicationActionExecutor.buildTransferDefinition(rd, nodes);
       assertEquals(true, td.isSync());
       assertEquals(replicationParams.getTransferReadOnly(), td.isReadOnly());
       assertEquals(2, td.getNodes().size());
       assertEquals(true, td.getNodes().contains(folder1));
       assertEquals(true, td.getNodes().contains(content1_1));
    }
    
    private abstract class DoInTransaction implements RetryingTransactionCallback<Void>
    {
       protected final ReplicationDefinition replicationDefinition;
       private DoInTransaction(ReplicationDefinition rd)
       {
          this.replicationDefinition = rd;
       }
    }
    
    /**
     * Test that the schedule related parts work properly
     */
    public void testScheduling() throws Exception
    {
       // A new definition doesn't have scheduling
       ReplicationDefinition rd = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
       rd.setTargetName("Target");
       assertFalse(rd.isSchedulingEnabled());
       
       
       // Disable does nothing
       replicationService.disableScheduling(rd);
       assertFalse(rd.isSchedulingEnabled());
       
       
       // Enable it
       transactionService.getRetryingTransactionHelper().doInTransaction(
          new DoInTransaction(rd) {
             public Void execute() throws Throwable {
                replicationService.saveReplicationDefinition(replicationDefinition);
                replicationService.enableScheduling(replicationDefinition);
                assertTrue(replicationDefinition.isSchedulingEnabled());
                return null;
             }
          }, false, true
       );
       
       assertTrue(rd.isSchedulingEnabled());
       
       
       // Double enabling does nothing
       replicationService.enableScheduling(rd);
       assertTrue(rd.isSchedulingEnabled());
       
       
       // Change it
       assertNull(rd.getScheduleStart());
       assertNull(rd.getScheduleIntervalCount());
       assertNull(rd.getScheduleIntervalPeriod());
       
       rd.setScheduleStart(new Date(1));
       
       assertEquals(1, rd.getScheduleStart().getTime());
       assertEquals(null, rd.getScheduleIntervalCount());
       assertEquals(null, rd.getScheduleIntervalPeriod());
       
       
       // Won't show up until saved
       ReplicationDefinition rd2 = replicationService.loadReplicationDefinition(ACTION_NAME);
       assertEquals(false, rd2.isSchedulingEnabled());
       assertEquals(null, rd2.getScheduleStart());
       assertEquals(null, rd2.getScheduleIntervalCount());
       assertEquals(null, rd2.getScheduleIntervalPeriod());
       
       
       // Save and check
       assertEquals(true, rd.isSchedulingEnabled());
       
       transactionService.getRetryingTransactionHelper().doInTransaction(
          new DoInTransaction(rd) {
             public Void execute() throws Throwable {
                replicationService.saveReplicationDefinition(replicationDefinition);
                return null;
             }
          }, false, true
       );
       
       assertEquals(true, rd.isSchedulingEnabled());
       assertEquals(1, rd.getScheduleStart().getTime());
       assertEquals(null, rd.getScheduleIntervalCount());
       assertEquals(null, rd.getScheduleIntervalPeriod());
       
       rd = replicationService.loadReplicationDefinition(ACTION_NAME);
       assertEquals(true, rd.isSchedulingEnabled());
       assertEquals(1, rd.getScheduleStart().getTime());
       assertEquals(null, rd.getScheduleIntervalCount());
       assertEquals(null, rd.getScheduleIntervalPeriod());
       
       
       // Change, save, check
       rd.setScheduleIntervalCount(2);
       rd.setScheduleIntervalPeriod(IntervalPeriod.Hour);
       
       assertEquals(true, rd.isSchedulingEnabled());
       assertEquals(1, rd.getScheduleStart().getTime());
       assertEquals(2, rd.getScheduleIntervalCount().intValue());
       assertEquals(IntervalPeriod.Hour, rd.getScheduleIntervalPeriod());
       
       transactionService.getRetryingTransactionHelper().doInTransaction(
          new DoInTransaction(rd) {
             public Void execute() throws Throwable {
                replicationService.saveReplicationDefinition(replicationDefinition);
                return null;
             }
          }, false, true
       );
       
       assertEquals(true, rd.isSchedulingEnabled());
       assertEquals(1, rd.getScheduleStart().getTime());
       assertEquals(2, rd.getScheduleIntervalCount().intValue());
       assertEquals(IntervalPeriod.Hour, rd.getScheduleIntervalPeriod());
       
       rd = replicationService.loadReplicationDefinition(ACTION_NAME);
       assertEquals(true, rd.isSchedulingEnabled());
       assertEquals(1, rd.getScheduleStart().getTime());
       assertEquals(2, rd.getScheduleIntervalCount().intValue());
       assertEquals(IntervalPeriod.Hour, rd.getScheduleIntervalPeriod());
       
       
       // Re-load and enable is fine
       rd2 = replicationService.loadReplicationDefinition(ACTION_NAME);
       assertEquals(true, rd2.isSchedulingEnabled());
       replicationService.enableScheduling(rd2);
       assertEquals(true, rd2.isSchedulingEnabled());
       
       
       // Check on the listing methods
       assertEquals(1, replicationService.loadReplicationDefinitions().size());
       rd = replicationService.loadReplicationDefinitions().get(0);
       assertEquals(true, rd.isSchedulingEnabled());
       assertEquals(1, rd.getScheduleStart().getTime());
       assertEquals(2, rd.getScheduleIntervalCount().intValue());
       assertEquals(IntervalPeriod.Hour, rd.getScheduleIntervalPeriod());
       
       assertEquals(1, replicationService.loadReplicationDefinitions("Target").size());
       rd = replicationService.loadReplicationDefinitions("Target").get(0);
       assertEquals(true, rd.isSchedulingEnabled());
       assertEquals(1, rd.getScheduleStart().getTime());
       assertEquals(2, rd.getScheduleIntervalCount().intValue());
       assertEquals(IntervalPeriod.Hour, rd.getScheduleIntervalPeriod());
       
       
        // Disable it
        transactionService.getRetryingTransactionHelper().doInTransaction(new DoInTransaction(rd)
        {
            public Void execute() throws Throwable
            {
                replicationService.disableScheduling(replicationDefinition);
                return null;
            }
        });
       assertEquals(false, rd.isSchedulingEnabled());
       
       
       // Check listings again
       rd = replicationService.loadReplicationDefinitions().get(0);
       assertEquals(false, rd.isSchedulingEnabled());
       
       rd = replicationService.loadReplicationDefinitions("Target").get(0);
       assertEquals(false, rd.isSchedulingEnabled());
       
       
       // Enable it, and check the scheduled service
       final int count = scheduledPersistedActionService.listSchedules().size();
       transactionService.getRetryingTransactionHelper().doInTransaction(
          new DoInTransaction(rd) {
             public Void execute() throws Throwable {
                replicationService.enableScheduling(replicationDefinition);
                replicationService.saveReplicationDefinition(replicationDefinition);
                assertEquals(count+1, scheduledPersistedActionService.listSchedules().size());
                return null;
             }
          }, false, true
       );
       
       
       // Delete it, and check the scheduled service
       transactionService.getRetryingTransactionHelper().doInTransaction(
           new RetryingTransactionCallback<Void>() {
              public Void execute() throws Throwable {
                 ReplicationDefinition replicationDefinition;
                 replicationDefinition = replicationService.loadReplicationDefinition(ACTION_NAME);
                 replicationService.deleteReplicationDefinition(replicationDefinition);
                 assertEquals(count, scheduledPersistedActionService.listSchedules().size());
                 return null;
              }
           }, false, true
       );
       assertEquals(count, scheduledPersistedActionService.listSchedules().size());
       
       
       // Ask for it to run scheduled
       // Should fire up and then fail due to missing definitions
       transactionService.getRetryingTransactionHelper().doInTransaction(
          new RetryingTransactionCallback<Void>() {
              public Void execute() throws Throwable {
                 ReplicationDefinition replicationDefinition;
                 replicationDefinition = replicationService.createReplicationDefinition(ACTION_NAME, "Test");
                 replicationService.enableScheduling(replicationDefinition);
                 replicationDefinition.setScheduleStart(new Date(System.currentTimeMillis()+50));
                 replicationService.saveReplicationDefinition(replicationDefinition);
                 assertEquals(ActionStatus.New, replicationDefinition.getExecutionStatus());
                 return null;
              }
           }, false, true
       );
       
       // Let it fire up, wait up to 1.5 seconds
       for(int i=0; i<150; i++)
       {
          rd = replicationService.loadReplicationDefinition(ACTION_NAME);
          if(rd.getExecutionStatus().equals(ActionStatus.Failed))
             break;
          if(rd.getExecutionStatus().equals(ActionStatus.Completed))
             break;
          Thread.sleep(10);
       }
       
       // Should have failed, as missing target + payload
       assertEquals(ActionStatus.Failed, rd.getExecutionStatus());
    }
    
    public void testJavascriptAPI() throws Exception
    {
       ServiceRegistry serviceRegistry = (ServiceRegistry)ctx.getBean("ServiceRegistry");
       
       // Setup some replication tasks
       ReplicationDefinition empty = replicationService.createReplicationDefinition(ACTION_NAME, "Empty");
       
       ReplicationDefinition persisted = replicationService.createReplicationDefinition(ACTION_NAME2, "Persisted");
       persisted.setTargetName(TRANSFER_TARGET);
       persisted.getPayload().add(
             new NodeRef("workspace://SpacesStore/Testing")
       );
       persisted.getPayload().add(
             new NodeRef("workspace://SpacesStore/Testing2")
       );
       replicationService.saveReplicationDefinition(persisted);
       
       ReplicationDefinition persisted2 = replicationService.createReplicationDefinition(ACTION_NAME3, "Persisted2");
       persisted2.setTargetName("AnotherTarget");
       replicationService.saveReplicationDefinition(persisted2);
       
       // Call the test 
       Map<String, Object> model = new HashMap<String, Object>();
       model.put("Empty", new ScriptReplicationDefinition(serviceRegistry, replicationService, null, empty));
       model.put("EmptyName", ACTION_NAME);
       model.put("Persisted", new ScriptReplicationDefinition(serviceRegistry, replicationService, null, persisted));
       model.put("PersistedName", ACTION_NAME2);
       model.put("PersistedNodeRef", persisted.getNodeRef().toString());
       model.put("PersistedTarget", persisted.getTargetName());
       model.put("Persisted2", new ScriptReplicationDefinition(serviceRegistry, replicationService, null, persisted2));
       model.put("Persisted2Name", ACTION_NAME3);
       model.put("Persisted2NodeRef", persisted2.getNodeRef().toString());
       model.put("Persisted2Target", persisted2.getTargetName());
       
       ScriptLocation location = new ClasspathScriptLocation("org/alfresco/repo/replication/script/test_replicationService.js");
       this.scriptService.executeScript(location, model);
    }
    
    // =============================================
    

    private NodeRef makeNode(NodeRef parent, QName nodeType)
    {
        String uuid = GUID.generate();
        return makeNode(parent, nodeType, uuid);
    }
    private NodeRef makeNode(NodeRef parent, QName nodeType, String name)
    {
        Map<QName, Serializable> props = new HashMap<QName, Serializable>();
        
        QName newName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, name);
        NodeRef existing = nodeService.getChildByName(parent, ContentModel.ASSOC_CONTAINS, name);
        if(existing != null) {
           System.err.println("Zapped existing node " + existing + " for name " + name);
           
           try {
              lockService.unlock(existing, true);
           } catch(UnableToReleaseLockException e) {}
           nodeService.deleteNode(existing);
        }
        
        props.put(ContentModel.PROP_NAME, name);
        ChildAssociationRef assoc = nodeService.createNode(parent, ContentModel.ASSOC_CONTAINS, newName, nodeType, props);
        return assoc.getChildRef();
    }
    
    private void makeTransferTarget() {
       String name = TRANSFER_TARGET;
       String title = "title";
       String description = "description";
       String endpointProtocol = "http";
       String endpointHost = "localhost";
       int endpointPort = 8080;
       String endpointPath = "rhubarb";
       String username = "admin";
       char[] password = "password".toCharArray();
     
       TransferTarget ret = transferService.createAndSaveTransferTarget(name, title, description, endpointProtocol, endpointHost, endpointPort, endpointPath, username, password);
       assertNotNull("Transfer Target not correctly built", ret);
    }
    
    private void makeTransferServiceLocal() {
       TransferReceiver receiver = (TransferReceiver)ctx.getBean("transferReceiver");
       TransferManifestNodeFactory transferManifestNodeFactory = (TransferManifestNodeFactory)ctx.getBean("transferManifestNodeFactory");
       TransferServiceImpl2 transferServiceImpl = (TransferServiceImpl2) ctx.getBean("transferService2");
       ContentService contentService = (ContentService) ctx.getBean("contentService");
       
       TransferTransmitter transmitter = 
          new UnitTestInProcessTransmitterImpl(receiver, contentService, transactionService);
       transferServiceImpl.setTransmitter(transmitter);
       
       UnitTestTransferManifestNodeFactory testNodeFactory = 
          new UnitTestTransferManifestNodeFactory(transferManifestNodeFactory); 
       transferServiceImpl.setTransferManifestNodeFactory(testNodeFactory);
       
       // Map company_home to the special destination folder
       List<Pair<Path, Path>> pathMap = testNodeFactory.getPathMap();
       pathMap.add(new Pair<Path,Path>(
             nodeService.getPath(repositoryHelper.getCompanyHome()),
             nodeService.getPath(destinationFolder)
       ));
    }
}
