/*
 * Copyright (C) 2005-2012 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * 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 .
 */
package org.alfresco.repo.action;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator;
import org.alfresco.repo.action.evaluator.InCategoryEvaluator;
import org.alfresco.repo.action.evaluator.NoConditionEvaluator;
import org.alfresco.repo.action.evaluator.compare.ComparePropertyValueOperation;
import org.alfresco.repo.action.executer.ActionExecuter;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.repo.action.executer.AddFeaturesActionExecuter;
import org.alfresco.repo.action.executer.CheckInActionExecuter;
import org.alfresco.repo.action.executer.CheckOutActionExecuter;
import org.alfresco.repo.action.executer.CompositeActionExecuter;
import org.alfresco.repo.action.executer.MoveActionExecuter;
import org.alfresco.repo.action.executer.ScriptActionExecuter;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.action.ActionConditionDefinition;
import org.alfresco.service.cmr.action.ActionDefinition;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.action.ActionServiceTransientException;
import org.alfresco.service.cmr.action.ActionStatus;
import org.alfresco.service.cmr.action.ActionTrackingService;
import org.alfresco.service.cmr.action.CancellableAction;
import org.alfresco.service.cmr.action.CompositeAction;
import org.alfresco.service.cmr.action.CompositeActionCondition;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
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.transaction.TransactionService;
import org.alfresco.test_category.BaseSpringTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.BaseAlfrescoSpringTest;
import org.alfresco.util.GUID;
import org.alfresco.util.PropertyMap;
import org.junit.experimental.categories.Category;
import org.springframework.context.ConfigurableApplicationContext;
/**
 * Action service test
 * 
 * @author Roy Wetherall
 */
@Category(BaseSpringTestsCategory.class)
public class ActionServiceImplTest extends BaseAlfrescoSpringTest
{
    private static final String BAD_NAME = "badName";
    
    private NodeRef nodeRef;
    private NodeRef folder;
    private RetryingTransactionHelper transactionHelper;
    
    @Override
    protected String[] getConfigLocations()
    {
        String[] existingConfigLocations = ApplicationContextHelper.CONFIG_LOCATIONS;
        List locations = Arrays.asList(existingConfigLocations);
        List mutableLocationsList = new ArrayList(locations);
        mutableLocationsList.add("classpath:org/alfresco/repo/action/test-action-services-context.xml");
        
        String[] result = mutableLocationsList.toArray(new String[mutableLocationsList.size()]);
        return result;
    }
    
    @Override
    protected void onSetUpInTransaction() throws Exception
    {
        super.onSetUpInTransaction();
        this.transactionHelper = (RetryingTransactionHelper)this.applicationContext.getBean("retryingTransactionHelper");
        // Create the node used for tests
        this.nodeRef = this.nodeService.createNode(
                this.rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}testnode"),
                ContentModel.TYPE_CONTENT).getChildRef();
        this.nodeService.setProperty(
                this.nodeRef,
                ContentModel.PROP_CONTENT,
                new ContentData(null, MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, null));
        this.folder = this.nodeService.createNode(
                this.rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName("{test}testFolder"),
                ContentModel.TYPE_FOLDER).getChildRef();
        
        // Register the test executor, if needed
        SleepActionExecuter.registerIfNeeded(applicationContext);
    }
    
    /**
     * Test getActionDefinition
     */
    public void testGetActionDefinition()
    {
        ActionDefinition action = actionService.getActionDefinition(AddFeaturesActionExecuter.NAME);
        assertNotNull(action);
        assertEquals(AddFeaturesActionExecuter.NAME, action.getName());
        
        ActionConditionDefinition nullCondition = this.actionService.getActionConditionDefinition(BAD_NAME);
        assertNull(nullCondition);        
    }
    
    /**
     * Test getActionDefintions
     */
    public void testGetActionDefinitions()
    {
        List defintions = this.actionService.getActionDefinitions();
        assertNotNull(defintions);
        assertFalse(defintions.isEmpty());
        int totalCount = defintions.size();
        
        for (ActionDefinition definition : defintions)
        {
            System.out.println(definition.getTitle());
        }
        
        // Get the action defintions for a folder type (there should be less than the total available)
        List definitions = this.actionService.getActionDefinitions(this.folder);
        assertNotNull(definitions);
        assertTrue(totalCount > definitions.size());        
    }    
    /**
     * Test getActionConditionDefinition
     */
    public void testGetActionConditionDefinition()
    {
        ActionConditionDefinition condition = this.actionService.getActionConditionDefinition(NoConditionEvaluator.NAME);
        assertNotNull(condition);
        assertEquals(NoConditionEvaluator.NAME, condition.getName());
        
        ActionConditionDefinition nullCondition = this.actionService.getActionConditionDefinition(BAD_NAME);
        assertNull(nullCondition);    
    }
    /**
     * Test getActionConditionDefinitions
     *
     */
    public void testGetActionConditionDefinitions()
    {
        List defintions = this.actionService.getActionConditionDefinitions();
        assertNotNull(defintions);
        assertFalse(defintions.isEmpty());
        
        for (ActionConditionDefinition definition : defintions)
        {
            System.out.println(definition.getTitle());
        }
    }
    /**
     * Test create action condition
     */
    public void testCreateActionCondition()
    {
        ActionCondition condition = this.actionService.createActionCondition(NoConditionEvaluator.NAME);
        assertNotNull(condition);
        assertEquals(NoConditionEvaluator.NAME, condition.getActionConditionDefinitionName());
        Map params = new HashMap<>(0);
        condition = this.actionService.createActionCondition(NoConditionEvaluator.NAME, params);
        assertNotNull(condition);
        assertEquals(NoConditionEvaluator.NAME, condition.getActionConditionDefinitionName());
    }
      /**
    * Test createCompositeAction
    */
   public void testCreateCompositeActionCondition()
   {
      CompositeActionCondition action = this.actionService.createCompositeActionCondition();
      assertNotNull(action);
      assertEquals(CompositeActionCondition.COMPOSITE_CONDITION, action.getActionConditionDefinitionName());
   }
    
    /**
     * Test createAction
     */
    public void testCreateAction()
    {
        Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        assertNotNull(action);
        assertEquals(AddFeaturesActionExecuter.NAME, action.getActionDefinitionName());
        Map params = new HashMap<>(0);
        action = this.actionService.createAction(AddFeaturesActionExecuter.NAME, params);
        assertNotNull(action);
        assertEquals(AddFeaturesActionExecuter.NAME, action.getActionDefinitionName());
    }
    /**
     * Test createCompositeAction
     */
    public void testCreateCompositeAction()
    {
        CompositeAction action = this.actionService.createCompositeAction();
        assertNotNull(action);
        assertEquals(CompositeActionExecuter.NAME, action.getActionDefinitionName());
    }
    /**
     * Evaluate action
     */
    public void testEvaluateAction()
    {
        Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        assertTrue(this.actionService.evaluateAction(action, this.nodeRef));
        
        ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME);
        condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc");
        action.addActionCondition(condition);
        
        assertFalse(this.actionService.evaluateAction(action, this.nodeRef));
        this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc");
        assertTrue(this.actionService.evaluateAction(action, this.nodeRef));
        
        ActionCondition condition2 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME);
        condition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "my");
        action.addActionCondition(condition2);
        assertTrue(this.actionService.evaluateAction(action, this.nodeRef));
        
        this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "document.doc");
        assertFalse(this.actionService.evaluateAction(action, this.nodeRef));
    }
    
    /**
     * Test evaluate action condition
     */
    public void testEvaluateActionCondition()
    {
        ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME);
        condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc");
        
        assertFalse(this.actionService.evaluateActionCondition(condition, this.nodeRef));
        this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc");
        assertTrue(this.actionService.evaluateActionCondition(condition, this.nodeRef));
        
        // Check that inverting the condition has the correct effect
        condition.setInvertCondition(true);
        assertFalse(this.actionService.evaluateActionCondition(condition, this.nodeRef));
    }
    
   /**
    * Test evaluate action condition
    */
   public void testEvaluateCompositeActionConditionWith1SubCondition()
   {
      CompositeActionCondition compositeCondition = this.actionService.createCompositeActionCondition();
      
      ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME);
      condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc");
      compositeCondition.addActionCondition(condition);
      
      this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc");
      assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef));
        
      // Check that inverting the condition has the correct effect
      compositeCondition.setInvertCondition(true);
      assertFalse(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef));
   }
   /**
    * Test evaluate action condition
    */
   public void testEvaluateCompositeActionConditionWith2SubConditions()
   {
      CompositeActionCondition compositeCondition = this.actionService.createCompositeActionCondition();
      
      ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME);
      condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc");
      compositeCondition.addActionCondition(condition);
      
      this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc");
      assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef));
      ActionCondition conditionTwo = this.actionService.createActionCondition("compare-text-property");
      conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "Doc");
      conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_PROPERTY, QName.createQName(null, "name"));
      conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.CONTAINS.toString());
      compositeCondition.addActionCondition(conditionTwo);
      assertFalse(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef));
      
      compositeCondition.removeAllActionConditions();
      assertFalse(compositeCondition.hasActionConditions());
      
      compositeCondition.addActionCondition(condition);
      conditionTwo.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "NotFound");
      assertFalse(this.actionService.evaluateActionCondition(conditionTwo, this.nodeRef)); 
      
      compositeCondition.addActionCondition(conditionTwo);
      compositeCondition.setORCondition(true);
      assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef));
      compositeCondition.setORCondition(false);
      assertFalse(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef));
      
      // Check that inverting the condition has the correct effect
      compositeCondition.setInvertCondition(true);
      assertTrue(this.actionService.evaluateActionCondition(compositeCondition, this.nodeRef));
   }
   
    /**
     * Test execute action
     */
    public void testExecuteAction()
    {
        assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
        
        Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE);
        
        this.actionService.executeAction(action, this.nodeRef);
        assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
        
        this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE);
        assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
        
        ActionCondition condition = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME);
        condition.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc");
        action.addActionCondition(condition);
                
        this.actionService.executeAction(action, this.nodeRef);
        assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
        
        this.actionService.executeAction(action, this.nodeRef, true);
        assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
        
        this.actionService.executeAction(action, this.nodeRef, false);
        assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
        
        this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE);
        assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
        
        this.nodeService.setProperty(this.nodeRef, ContentModel.PROP_NAME, "myDocument.doc");
        this.actionService.executeAction(action, this.nodeRef);
        assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
        
        this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE);
        assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
        
        this.nodeService.removeAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE);
        assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
        
        // Create the composite action
        Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_LOCKABLE);
        Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        action2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE);        
        CompositeAction compAction = this.actionService.createCompositeAction();
        compAction.setTitle("title");
        compAction.setDescription("description");
        compAction.addAction(action1);
        compAction.addAction(action2);
        
        // Execute the composite action
        this.actionService.executeAction(compAction, this.nodeRef);
        
        assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_LOCKABLE));
        assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
    }    
    public void testGetAndGetAllWithNoActions()
    {
        assertNull(this.actionService.getAction(this.nodeRef, AddFeaturesActionExecuter.NAME));
        List actions = this.actionService.getActions(this.nodeRef);
        assertNotNull(actions);
        assertEquals(0, actions.size());
    }
	
    public void testExecuteActionWithNoParameterDef()
    {
        Action action = this.actionService.createAction("empty-action");
        this.actionService.executeAction(action, this.nodeRef);
        assertTrue("If we got here then the test is successful", true);
    }
    
    /**
     * Test saving an action with no conditions.  Includes testing storage and retrieval 
     * of compensating actions.
     */
    public void testSaveActionNoCondition()
    {
        // Create the action
        Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        String actionId = action.getId();
        
        // Set the parameters of the action
        action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE);
        
        // Set the title and description of the action
        action.setTitle("title");
        action.setDescription("description");
        action.setExecuteAsynchronously(true);
                
        // Save the action
        this.actionService.saveAction(this.nodeRef, action);
        
        // Get the action
        Action savedAction = this.actionService.getAction(this.nodeRef, actionId);
        
        // Check the action 
        assertEquals(action.getId(), savedAction.getId());
        assertEquals(action.getActionDefinitionName(), savedAction.getActionDefinitionName());
        
        // Check the properties
        assertEquals("title", savedAction.getTitle());
        assertEquals("description", savedAction.getDescription());
        assertTrue(savedAction.getExecuteAsychronously());
        
        // Check that the compensating action has not been set
        assertNull(savedAction.getCompensatingAction());
        
        // Check the properties
        assertEquals(1, savedAction.getParameterValues().size());
        assertEquals(ContentModel.ASPECT_VERSIONABLE, savedAction.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME));
                
        // Check the conditions
        assertNotNull(savedAction.getActionConditions());
        assertEquals(0, savedAction.getActionConditions().size());
        
        // Edit the properties of the action        
        Map properties = new HashMap(1);
        properties.put(ContentModel.PROP_NAME, "testName");
        action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_AUDITABLE);
        
        // Set the compensating action
        Action compensatingAction = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE);
        action.setCompensatingAction(compensatingAction);
        
        this.actionService.saveAction(this.nodeRef, action);
        Action savedAction2 = this.actionService.getAction(this.nodeRef, actionId);
        
        // Check the updated properties
        assertEquals(1, savedAction2.getParameterValues().size());
        assertEquals(ContentModel.ASPECT_AUDITABLE, savedAction2.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME));
        
        // Check the compensating action
        Action savedCompensatingAction = savedAction2.getCompensatingAction();
        assertNotNull(savedCompensatingAction);
        assertEquals(compensatingAction, savedCompensatingAction);
        assertEquals(AddFeaturesActionExecuter.NAME, savedCompensatingAction.getActionDefinitionName());
        assertEquals(ContentModel.ASPECT_VERSIONABLE, savedCompensatingAction.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME));
        
        // Change the details of the compensating action (edit and remove)
        compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE);
        this.actionService.saveAction(this.nodeRef, action);
        Action savedAction3 = this.actionService.getAction(this.nodeRef, actionId);
        Action savedCompensatingAction2 = savedAction3.getCompensatingAction();
        assertNotNull(savedCompensatingAction2);
        assertEquals(compensatingAction, savedCompensatingAction2);
        assertEquals(AddFeaturesActionExecuter.NAME, savedCompensatingAction2.getActionDefinitionName());
        assertEquals(ContentModel.ASPECT_CLASSIFIABLE, savedCompensatingAction2.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME));
        action.setCompensatingAction(null);
        this.actionService.saveAction(this.nodeRef, action);
        Action savedAction4 = this.actionService.getAction(this.nodeRef, actionId);
        assertNull(savedAction4.getCompensatingAction());
        
        //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef));
    }
    public void testOwningNodeRef()
    {
        // Create the action
        Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        String actionId = action.getId();
        
        // Set the parameters of the action
        action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE);
        
        // Set the title and description of the action
        action.setTitle("title");
        action.setDescription("description");
        action.setExecuteAsynchronously(true);
        
        // Check the owning node ref
        //assertNull(action.getOwningNodeRef());
                
        // Save the action
        this.actionService.saveAction(this.nodeRef, action);
        
        // Get the action
        this.actionService.getAction(this.nodeRef, actionId);        
    }
    /**
     * Test saving an action with conditions
     */
    public void testSaveActionWithConditions()
    {
        // Create the action
        Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        String actionId = action.getId();
        
        // Set the parameters of the action
        action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE);
        
        // Set the conditions of the action
        ActionCondition actionCondition = this.actionService.createActionCondition(NoConditionEvaluator.NAME);
        actionCondition.setInvertCondition(true);
        ActionCondition actionCondition2 = this.actionService.createActionCondition(ComparePropertyValueEvaluator.NAME);
        actionCondition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.doc");
        action.addActionCondition(actionCondition);
        action.addActionCondition(actionCondition2);
        
        // Save the action
        this.actionService.saveAction(this.nodeRef, action);
        
        // Get the action
        Action savedAction = this.actionService.getAction(this.nodeRef, actionId);
        
        // Check the action 
        assertEquals(action.getId(), savedAction.getId());
        assertEquals(action.getActionDefinitionName(), savedAction.getActionDefinitionName());
        
        // Check the properties
        assertEquals(action.getParameterValues().size(), savedAction.getParameterValues().size());
        assertEquals(ContentModel.ASPECT_VERSIONABLE, savedAction.getParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME));
        
        // Check the conditions
        assertNotNull(savedAction.getActionConditions());
        assertEquals(2, savedAction.getActionConditions().size());
        for (ActionCondition savedCondition : savedAction.getActionConditions())
        {
            if (savedCondition.getActionConditionDefinitionName().equals(NoConditionEvaluator.NAME) == true)
            {
                assertEquals(0, savedCondition.getParameterValues().size());
                assertTrue(savedCondition.getInvertCondition());
            }
            else if (savedCondition.getActionConditionDefinitionName().equals(ComparePropertyValueEvaluator.NAME) == true)
            {
                assertEquals(1, savedCondition.getParameterValues().size());
                assertEquals("*.doc", savedCondition.getParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE));
                assertFalse(savedCondition.getInvertCondition());
            }
            else
            {
                fail("There is a condition here that we are not expecting.");
            }
        }
        
        // Modify the conditions of the action
        ActionCondition actionCondition3 = this.actionService.createActionCondition(InCategoryEvaluator.NAME);
        actionCondition3.setParameterValue(InCategoryEvaluator.PARAM_CATEGORY_ASPECT, ContentModel.ASPECT_OWNABLE);
        action.addActionCondition(actionCondition3);
        action.removeActionCondition(actionCondition);
        actionCondition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE, "*.exe");
        actionCondition2.setParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION, ComparePropertyValueOperation.EQUALS);
        
        this.actionService.saveAction(this.nodeRef, action);
        Action savedAction2 = this.actionService.getAction(this.nodeRef, actionId);
        
        // Check that the conditions have been updated correctly
        assertNotNull(savedAction2.getActionConditions());
        assertEquals(2, savedAction2.getActionConditions().size());
        for (ActionCondition savedCondition : savedAction2.getActionConditions())
        {
            if (savedCondition.getActionConditionDefinitionName().equals(InCategoryEvaluator.NAME) == true)
            {
                assertEquals(1, savedCondition.getParameterValues().size());
                assertEquals(ContentModel.ASPECT_OWNABLE, savedCondition.getParameterValue(InCategoryEvaluator.PARAM_CATEGORY_ASPECT));
            }
            else if (savedCondition.getActionConditionDefinitionName().equals(ComparePropertyValueEvaluator.NAME) == true)
            {
                assertEquals(2, savedCondition.getParameterValues().size());
                assertEquals("*.exe", savedCondition.getParameterValue(ComparePropertyValueEvaluator.PARAM_VALUE));
                assertEquals(ComparePropertyValueOperation.EQUALS, savedCondition.getParameterValue(ComparePropertyValueEvaluator.PARAM_OPERATION));
            }
            else
            {
                fail("There is a condition here that we are not expecting.");
            }
        }
        
        //System.out.println(NodeStoreInspector.dumpNodeStore(this.nodeService, this.testStoreRef));
    }
    
    /**
     * Test saving a composite action
     */
    public void testSaveCompositeAction()
    {
        Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        Action action2 = this.actionService.createAction(CheckInActionExecuter.NAME);
        
        CompositeAction compositeAction = this.actionService.createCompositeAction();
        String actionId = compositeAction.getId();
        compositeAction.addAction(action1);
        compositeAction.addAction(action2);
        
        this.actionService.saveAction(this.nodeRef, compositeAction);
        assertEquals(1, this.actionService.getActions(this.nodeRef).size());
        CompositeAction savedCompositeAction = (CompositeAction)this.actionService.getAction(this.nodeRef, actionId);
        
        // Check the saved composite action
        assertEquals(2, savedCompositeAction.getActions().size());
        for (Action action : savedCompositeAction.getActions())
        {
            if (action.getActionDefinitionName().equals(AddFeaturesActionExecuter.NAME) == true)
            {
                assertEquals(action, action1);
            }
            else if (action.getActionDefinitionName().equals(CheckInActionExecuter.NAME) == true)
            {
                assertEquals(action, action2);
            }
            else
            {
                fail("We have an action here we are not expecting.");
            }
        }
        
        // Change the actions and re-save
        compositeAction.removeAction(action1);
        Action action3 = this.actionService.createAction(CheckOutActionExecuter.NAME);
        compositeAction.addAction(action3);
        action2.setParameterValue(CheckInActionExecuter.PARAM_DESCRIPTION, "description");
        
        this.actionService.saveAction(this.nodeRef, compositeAction);
        assertEquals(1, this.actionService.getActions(this.nodeRef).size());
        CompositeAction savedCompositeAction2 = (CompositeAction)this.actionService.getAction(this.nodeRef, actionId);
        
        assertEquals(2, savedCompositeAction2.getActions().size());
        for (Action action : savedCompositeAction2.getActions())
        {
            if (action.getActionDefinitionName().equals(CheckOutActionExecuter.NAME) == true)
            {
                assertEquals(action, action3);
            }
            else if (action.getActionDefinitionName().equals(CheckInActionExecuter.NAME) == true)
            {
                assertEquals(action, action2);
                assertEquals("description", action2.getParameterValue(CheckInActionExecuter.PARAM_DESCRIPTION));
            }
            else
            {
                fail("We have an action here we are not expecting.");
            }
        }
    }
    
    /**
     * Test remove action
     */
    public void testRemove()
    {
        assertEquals(0, this.actionService.getActions(this.nodeRef).size());
        
        Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        this.actionService.saveAction(this.nodeRef, action1);
        Action action2 = this.actionService.createAction(CheckInActionExecuter.NAME);
        this.actionService.saveAction(this.nodeRef, action2);        
        assertEquals(2, this.actionService.getActions(this.nodeRef).size());
        
        this.actionService.removeAction(this.nodeRef, action1);
        assertEquals(1, this.actionService.getActions(this.nodeRef).size());
        
        this.actionService.removeAllActions(this.nodeRef);
        assertEquals(0, this.actionService.getActions(this.nodeRef).size());        
    }
    
    public void testConditionOrder()
    {
        Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        String actionId = action.getId();
        
        ActionCondition condition1 = this.actionService.createActionCondition(NoConditionEvaluator.NAME);
        ActionCondition condition2 = this.actionService.createActionCondition(NoConditionEvaluator.NAME);
        
        action.addActionCondition(condition1);
        action.addActionCondition(condition2);
        
        this.actionService.saveAction(this.nodeRef, action);
        Action savedAction = this.actionService.getAction(this.nodeRef, actionId);
        
        // Check that the conditions have been retrieved in the correct order
        assertNotNull(savedAction);
        assertEquals(condition1, savedAction.getActionCondition(0));
        assertEquals(condition2, savedAction.getActionCondition(1));
        
        ActionCondition condition3 = this.actionService.createActionCondition(NoConditionEvaluator.NAME);
        ActionCondition condition4 = this.actionService.createActionCondition(NoConditionEvaluator.NAME);
        
        // Update the conditions on the action
        savedAction.removeActionCondition(condition1);
        savedAction.addActionCondition(condition3);
        savedAction.addActionCondition(condition4);
        
        this.actionService.saveAction(this.nodeRef, savedAction);
        Action savedAction2 = this.actionService.getAction(this.nodeRef, actionId);
        
        // Check that the conditions are still in the correct order
        assertNotNull(savedAction2);
        assertEquals(condition2, savedAction2.getActionCondition(0));
        assertEquals(condition3, savedAction2.getActionCondition(1));
        assertEquals(condition4, savedAction2.getActionCondition(2));
    }
    
    public void testActionOrder()
    {
        CompositeAction action = this.actionService.createCompositeAction();
        String actionId = action.getId();
        
        Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        
        action.addAction(action1);
        action.addAction(action2);
        
        this.actionService.saveAction(this.nodeRef, action);
        CompositeAction savedAction = (CompositeAction)this.actionService.getAction(this.nodeRef, actionId);
        
        // Check that the conditions have been retrieved in the correct order
        assertNotNull(savedAction);
        assertEquals(action1, savedAction.getAction(0));
        assertEquals(action2, savedAction.getAction(1));
        
        Action action3 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        Action action4 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        
        // Update the conditions on the action
        savedAction.removeAction(action1);
        savedAction.addAction(action3);
        savedAction.addAction(action4);
        
        this.actionService.saveAction(this.nodeRef, savedAction);
        CompositeAction savedAction2 = (CompositeAction)this.actionService.getAction(this.nodeRef, actionId);
        
        // Check that the conditions are still in the correct order
        assertNotNull(savedAction2);
        assertEquals(action2, savedAction2.getAction(0));
        assertEquals(action3, savedAction2.getAction(1));
        assertEquals(action4, savedAction2.getAction(2));
    }
    
    /**
     * Test the action result parameter
     */
    public void testActionResult()
    {
        // Create the script node reference
        NodeRef script = this.nodeService.createNode(
                this.folder,
                ContentModel.ASSOC_CONTAINS,
                QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testScript.js"),
                ContentModel.TYPE_CONTENT).getChildRef();
        this.nodeService.setProperty(script, ContentModel.PROP_NAME, "testScript.js");
        ContentWriter contentWriter = this.contentService.getWriter(script, ContentModel.PROP_CONTENT, true);
        contentWriter.setMimetype("text/plain");
        contentWriter.setEncoding("UTF-8");
        contentWriter.putContent("\"VALUE\";");
        
        // Create the action
        Action action1 = this.actionService.createAction(ScriptActionExecuter.NAME);
        action1.setParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF, script);
        
        // Execute the action
        this.actionService.executeAction(action1, this.nodeRef);
        
        // Get the result
        String result = (String)action1.getParameterValue(ActionExecuter.PARAM_RESULT);
        assertNotNull(result);
        assertEquals("VALUE", result);                
    }
    
    /** ===================================================================================
     *  Test asynchronous actions
     */
    
    /**
     * This test checks that a series of "equivalent" actions submitted for asynchronous execution
     * will be correctly filtered so that no 2 equivalent actions are executed at the same time.
     */
    public void offtestAsyncLongRunningActionsFilter()
    {
    	setComplete();
    	endTransaction();
    	final SleepActionExecuter sleepAction = (SleepActionExecuter)applicationContext.getBean("sleep-action");
		assertNotNull(sleepAction);
		sleepAction.setSleepMs(10);
		
		final int actionSubmissonCount = 4; // Rather arbitrary count.
		for (int i = 0; i < actionSubmissonCount; i ++)
		{
	        transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
	                {
	                    public Void execute() throws Throwable
	                    {
	                    	Action action = actionService.createAction(SleepActionExecuter.NAME);
	                    	action.setExecuteAsynchronously(true);
	                    	
	                    	actionService.executeAction(action, nodeRef);
	                        return null;
	                    }          
	                });        
		}
        // Wait long enough for previous action(s) to have executed and then submit another
        try
        {
	  		Thread.sleep(sleepAction.getSleepMs() * actionSubmissonCount + 1000); // Enough time for all actions and an extra second for luck.
  		}
        catch (InterruptedException ignored)
		{
			// intentionally empty
		}
        transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
                {
                    public Void execute() throws Throwable
                    {
                    	Action action = actionService.createAction(SleepActionExecuter.NAME);
                    	action.setExecuteAsynchronously(true);
                    	
                    	actionService.executeAction(action, nodeRef);
                        return null;
                    }          
                });        
        try
        {
	  		Thread.sleep(sleepAction.getSleepMs() + 2000); // Enough time for latest action and an extra 2 seconds for luck.
  		}
        catch (InterruptedException ignored)
		{
			// intentionally empty
		}
        
        int sleepTime = 0; // Do not sleep during execution as the Action itself sleeps.
		int maxTries = 1;
		postAsyncActionTest(
                this.transactionService,
                sleepTime, 
                maxTries, 
                new AsyncTest()
                {
                    public String executeTest()
                    {
                    	final int expectedResult = 2;
                    	int actualResult = sleepAction.getTimesExecuted();
                    	return actualResult == expectedResult ? null : "Expected timesExecuted " + expectedResult + " was " + actualResult;
                    };
                });
    }
    /**
     * Test asynchronous execute action
     */
    public void testAsyncExecuteAction()
    {
        assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE));
        
        Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE);
        action.setExecuteAsynchronously(true);
        
        this.actionService.executeAction(action, this.nodeRef);
        
        setComplete();
        endTransaction();
        
        final NodeService finalNodeService = this.nodeService;
        final NodeRef finalNodeRef = this.nodeRef;
        
        postAsyncActionTest(
                this.transactionService,
                1000l, 
                10, 
                new AsyncTest()
                {
                    public String executeTest() 
                    {
                    	boolean result = finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_CLASSIFIABLE);
                    	return result ? null : "Expected aspect Classifiable";
                    };
                });
    }    
    
    
    
    /**
     * Test async composite action execution
     */
    public void testAsyncCompositeActionExecute()
    {
        // Create the composite action
        Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_LOCKABLE);
        Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        action2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE);        
        CompositeAction compAction = this.actionService.createCompositeAction();
        compAction.setTitle("title");
        compAction.setDescription("description");
        compAction.addAction(action1);
        compAction.addAction(action2);
        compAction.setExecuteAsynchronously(true);
        
        // Execute the composite action
        this.actionService.executeAction(compAction, this.nodeRef);
        
        setComplete();
        endTransaction();
        
        final NodeService finalNodeService = this.nodeService;
        final NodeRef finalNodeRef = this.nodeRef;
        
        postAsyncActionTest(
                this.transactionService,
                1000, 
                10, 
                new AsyncTest()
                {
                    public String executeTest() 
                    {
                    	boolean result = finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_VERSIONABLE) &&
                        finalNodeService.hasAspect(finalNodeRef, ContentModel.ASPECT_LOCKABLE);
                    	return result ? null : "Expected aspects Versionable & Lockable";
                    };
                });
    }
    
    public void xtestAsyncLoadTest()
    {
        // TODO this is very weak .. how do we improve this ???
        
        Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_VERSIONABLE);
        action.setExecuteAsynchronously(true);
        
        for (int i = 0; i < 1000; i++)
        {
            this.actionService.executeAction(action, this.nodeRef);
        }        
        
        setComplete();
        endTransaction();
        
        // TODO how do we assess whether the large number of actions stacked cause a problem ??
    }
    
    /**
     * 
     * @param sleepTime
     * @param maxTries
     * @param test
     */
    public static void postAsyncActionTest(
            TransactionService transactionService,
            final long sleepTime, 
            final int maxTries, 
            final AsyncTest test)
    {
        try
        {
            int tries = 0;
            String errorMsg = null;
            while (errorMsg == null && tries < maxTries)
            {
                try
                {
                    // Increment the tries counter
                    tries++;
                    
                    // Sleep for a bit
                    Thread.sleep(sleepTime);
                    
                    errorMsg = (transactionService.getRetryingTransactionHelper().doInTransaction(
                                new RetryingTransactionCallback()
                                {
                                    public String execute()
                                    {    
                                        // See if the action has been performed
                                        String done = test.executeTest();
                                        return done;
                                    }                    
                                }));
                } 
                catch (InterruptedException e)
                {
                    // Do nothing
                    e.printStackTrace();
                }
            }
            
            if (errorMsg != null)
            {
                throw new RuntimeException("Asynchronous action was not executed. " + errorMsg);
            }
        }
        catch (Throwable exception)
        {
            exception.printStackTrace();
            fail("An exception was encountered whilst checking the async action was executed: " + exception.getMessage());
        }
    }
    
    /**
     * Async test interface
     */
    public interface AsyncTest
    {
    	/**
    	 * 
    	 * @return null if the test succeeded, else an error message for use in JUnit report.
    	 */
        String executeTest();        
    }
        
    /** ===================================================================================
     *  Test failure behaviour
     */
    
    /**
     * Test sync failure behaviour
     */
    public void testSyncFailureBehaviour()
    {
        // Create an action that is going to fail
        Action action = createFailingMoveAction(true);
        
        try
        {
            this.actionService.executeAction(action, this.nodeRef);
            
            // Fail if we get there since the exception should have been raised
            fail("An exception should have been raised.");
        }
        catch (RuntimeException exception)
        {
            // Good!  The exception was raised correctly
        }
        
        // Test what happens when a element of a composite action fails (should raise and bubble up to parent bahviour)        
        // Create the composite action
        Action action1 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        action1.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_LOCKABLE);
        Action action2 = this.actionService.createAction(AddFeaturesActionExecuter.NAME);
        action2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, QName.createQName("{test}badDogAspect"));        
        CompositeAction compAction = this.actionService.createCompositeAction();
        compAction.setTitle("title");
        compAction.setDescription("description");
        compAction.addAction(action1);
        compAction.addAction(action2);
        
        try
        {
            // Execute the composite action
            this.actionService.executeAction(compAction, this.nodeRef);
            
            fail("An exception should have been raised here !!");
        }
        catch (RuntimeException runtimeException)
        {
            // Good! The exception was raised
        }        
    }
    
    /**
     * Test the compensating action
     */
    public void testCompensatingAction()
    {
        // Create actions that are going to fail
        final Action fatalAction = createFailingMoveAction(true);
        final Action nonfatalAction = createFailingMoveAction(false);
        fatalAction.setTitle("fatal title");
        nonfatalAction.setTitle("non-fatal title");
        
        // Create the compensating actions
        Action compensatingAction = actionService.createAction(AddFeaturesActionExecuter.NAME);
        compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE);
        compensatingAction.setTitle("title");
        fatalAction.setCompensatingAction(compensatingAction);
        
        Action compensatingAction2 = actionService.createAction(AddFeaturesActionExecuter.NAME);
        compensatingAction2.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_TEMPORARY);
        compensatingAction2.setTitle("title");
        nonfatalAction.setCompensatingAction(compensatingAction2);
        
        
        // Set the actions to execute asynchronously
        fatalAction.setExecuteAsynchronously(true);
        nonfatalAction.setExecuteAsynchronously(true);
        
        this.actionService.executeAction(fatalAction, this.nodeRef);
        this.actionService.executeAction(nonfatalAction, this.nodeRef);
        
        setComplete();
        endTransaction();
        
        postAsyncActionTest(
                this.transactionService,
                1000, 
                10, 
                new AsyncTest()
                {
                    public String executeTest() 
                    {
                        boolean fatalCompensatingActionRun = ActionServiceImplTest.this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CLASSIFIABLE);
                        
                        boolean nonFatalCompensatingActionRun = ActionServiceImplTest.this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY);
                        
                        StringBuilder result = new StringBuilder();
                        if (!fatalCompensatingActionRun)
                        {
                            result.append("Expected aspect Classifiable.");
                        }
                        if (nonFatalCompensatingActionRun)
                        {
                            result.append(" Did not expect aspect Temporary");
                        }
                        
                        return ( !fatalCompensatingActionRun || nonFatalCompensatingActionRun ? result.toString() : null);
                    };
                });
        
        // Modify the compensating action so that it will also fail
        compensatingAction.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, QName.createQName("{test}badAspect"));
        
        this.transactionService.getRetryingTransactionHelper().doInTransaction(
                new RetryingTransactionCallback