diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index abdea3384c..0c78de8370 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -98,6 +98,9 @@ + + + diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java index 23372816df..c1ece9f01c 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java @@ -38,7 +38,11 @@ import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.TransactionListenerAdapter; +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; @@ -60,6 +64,7 @@ import org.alfresco.service.namespace.DynamicNamespacePrefixResolver; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -103,6 +108,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A private NodeService nodeService; private SearchService searchService; private DictionaryService dictionaryService; + private TransactionService transactionService; private AuthenticationContext authenticationContext; private PolicyComponent policyComponent; @@ -181,6 +187,16 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A this.dictionaryService = dictionaryService; } + /** + * Set the transaction service + * + * @param transactionService the transaction service + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + /** * @param policyComponent used to set up the action-based policy behaviour */ @@ -732,7 +748,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A { if (logger.isDebugEnabled() == true) { - logger.debug("Recording failure of action " + action + " due to " + exception.getMessage()); + logger.debug("Will shortly record failure of action " + action + " due to " + exception.getMessage()); } ((ActionImpl)action).setExecutionEndDate(new Date()); @@ -741,7 +757,56 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A if(action.getNodeRef() != null) { - // TODO + // Take a local copy of the details + // (That way, if someone has a reference to the + // action and plays with it, we still save the + // correct information) + final String actionId = action.getId(); + final Date startedAt = action.getExecutionStartDate(); + final Date endedAt = action.getExecutionEndDate(); + final String message = action.getExecutionFailureMessage(); + final NodeRef actionNode = action.getNodeRef(); + + // Have the details updated on the action as soon + // as the transaction has finished rolling back + AlfrescoTransactionSupport.bindListener( + new TransactionListenerAdapter() { + public void afterRollback() + { + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + // Update the action as the system user + return AuthenticationUtil.runAs(new RunAsWork() { + public Action doWork() throws Exception + { + // Grab the latest version of the action + ActionImpl action = (ActionImpl)createAction(actionNode); + + // Update it + action.setExecutionStartDate(startedAt); + action.setExecutionEndDate(endedAt); + action.setExecutionStatus(ActionStatus.Failed); + action.setExecutionFailureMessage(message); + saveActionImpl(actionNode, action); + + if (logger.isDebugEnabled() == true) + { + logger.debug("Recorded failure of action " + actionId + ", node " + actionNode + " due to " + message); + } + + // All done + return action; + } + }, AuthenticationUtil.SYSTEM_USER_NAME); + } + }, false, true + ); + } + } + ); } } diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java index 2a425ba68c..ce6ea5f8c0 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java @@ -68,7 +68,6 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest private NodeRef nodeRef; private NodeRef folder; - private RuntimeActionService runtimeActionService; private RetryingTransactionHelper transactionHelper; // @Override @@ -90,7 +89,6 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest super.onSetUpInTransaction(); this.transactionHelper = (RetryingTransactionHelper)this.applicationContext.getBean("retryingTransactionHelper"); - this.runtimeActionService = (RuntimeActionService)this.applicationContext.getBean("actionService"); // Create the node used for tests this.nodeRef = this.nodeService.createNode( @@ -1206,294 +1204,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest assertEquals(null, action.getExecutionFailureMessage()); } - /** - * Tests that when we run an action, either - * synchronously or asynchronously, with it - * working or failing, that the action execution - * service correctly sets the flags - */ - public void testExecutionTrackingOnExecution() throws Exception { - final SleepActionExecuter sleepActionExec = new SleepActionExecuter(); - sleepActionExec.setSleepMs(10); - Action action; - NodeRef actionNode; - - - // =========================================================== - // Execute a transient Action that works, synchronously - // =========================================================== - action = createWorkingSleepAction(); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef); - - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - - // =========================================================== - // Execute a transient Action that fails, synchronously - // =========================================================== - action = createFailingMoveAction(); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - try { - this.actionService.executeAction(action, this.nodeRef); - fail("Action should have failed, and the error been thrown"); - } catch(Exception e) {} - - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNotNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - - // Tidy up from the action failure - endTransaction(); - startNewTransaction(); - onSetUpInTransaction(); - - - // =========================================================== - // Execute a stored Action that works, synchronously - // =========================================================== - action = createWorkingSleepAction(); - this.actionService.saveAction(this.nodeRef, action); - actionNode = action.getNodeRef(); - assertNotNull(actionNode); - - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef); - - // Check our copy - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - // Now re-load and check the stored one - action = runtimeActionService.createAction(actionNode); - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - - // =========================================================== - // Execute a stored Action that fails, synchronously - // =========================================================== - action = createFailingMoveAction(); - this.actionService.saveAction(this.nodeRef, action); - actionNode = action.getNodeRef(); - assertNotNull(actionNode); - - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - try { - this.actionService.executeAction(action, this.nodeRef); - fail("Action should have failed, and the error been thrown"); - } catch(Exception e) {} - - // Check our copy - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNotNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - - // Now re-load and check the stored one - action = runtimeActionService.createAction(actionNode); - // TODO - Fix these -// assertNotNull(action.getExecutionStartDate()); -// assertNotNull(action.getExecutionEndDate()); -// assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); -// assertBefore(action.getExecutionEndDate(), new Date()); -// assertNotNull(action.getExecutionFailureMessage()); -// assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - - // Tidy up from the action failure - endTransaction(); - startNewTransaction(); - onSetUpInTransaction(); - - - // =========================================================== - // Execute a transient Action that works, asynchronously - // =========================================================== - action = createWorkingSleepAction(); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef, false, true); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Pending, action.getExecutionStatus()); - setComplete(); - // End the transaction. When run from a test, this call will - // block until the "async" action has finished running - endTransaction(); - Thread.sleep(100); - - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - // Put things back ready for the next check - startNewTransaction(); - onSetUpInTransaction(); - - - // =========================================================== - // Execute a transient Action that fails, asynchronously - // =========================================================== - action = createFailingMoveAction(); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef, false, true); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Pending, action.getExecutionStatus()); - setComplete(); - // End the transaction. When run from a test, this call will - // block until the "async" action has finished running - endTransaction(); - Thread.sleep(100); - - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNotNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - - // Put things back ready for the next check - startNewTransaction(); - onSetUpInTransaction(); - - - // =========================================================== - // Execute a stored Action that works, asynchronously - // =========================================================== - action = createWorkingSleepAction(); - this.actionService.saveAction(this.nodeRef, action); - actionNode = action.getNodeRef(); - assertNotNull(actionNode); - - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef, false, true); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Pending, action.getExecutionStatus()); - setComplete(); - // End the transaction. When run from a test, this call will - // block until the "async" action has finished running - endTransaction(); - Thread.sleep(100); - - // Check our copy - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - // Now re-load and check the stored one - action = runtimeActionService.createAction(actionNode); - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Completed, action.getExecutionStatus()); - - // Put things back ready for the next check - startNewTransaction(); - onSetUpInTransaction(); - - - // =========================================================== - // Execute a stored Action that fails, asynchronously - // =========================================================== - action = createFailingMoveAction(); - this.actionService.saveAction(this.nodeRef, action); - actionNode = action.getNodeRef(); - assertNotNull(actionNode); - - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.New, action.getExecutionStatus()); - - this.actionService.executeAction(action, this.nodeRef, false, true); - assertNull(action.getExecutionStartDate()); - assertNull(action.getExecutionEndDate()); - assertNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Pending, action.getExecutionStatus()); - setComplete(); - // End the transaction. When run from a test, this call will - // block until the "async" action has finished running - endTransaction(); - Thread.sleep(100); - - // Check our copy - assertNotNull(action.getExecutionStartDate()); - assertNotNull(action.getExecutionEndDate()); - assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); - assertBefore(action.getExecutionEndDate(), new Date()); - assertNotNull(action.getExecutionFailureMessage()); - assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - - // Now re-load and check the stored one - action = runtimeActionService.createAction(actionNode); - // TODO - Fix these -// assertNotNull(action.getExecutionStartDate()); -// assertNotNull(action.getExecutionEndDate()); -// assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); -// assertBefore(action.getExecutionEndDate(), new Date()); -// assertNotNull(action.getExecutionFailureMessage()); -// assertEquals(ActionStatus.Failed, action.getExecutionStatus()); - } - - private Action createFailingMoveAction() { + protected Action createFailingMoveAction() { Action failingAction = this.actionService.createAction(MoveActionExecuter.NAME); failingAction.setParameterValue(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); failingAction.setParameterValue(MoveActionExecuter.PARAM_ASSOC_QNAME, ContentModel.ASSOC_CHILDREN); @@ -1503,7 +1214,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest return failingAction; } - private Action createWorkingSleepAction() { + protected Action createWorkingSleepAction() { Action workingAction = actionService.createAction(SleepActionExecuter.NAME); return workingAction; } diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTransactionalTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTransactionalTest.java new file mode 100644 index 0000000000..1342b0c308 --- /dev/null +++ b/source/java/org/alfresco/repo/action/ActionServiceImplTransactionalTest.java @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2005-2010 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 static org.alfresco.repo.action.ActionServiceImplTest.assertBefore; + +import java.util.Date; +import java.util.List; + +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.repo.action.executer.MoveActionExecuter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.ActionStatus; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Action service tests which need careful control + * over the transactions they use. + * + * @author Nick Burch + */ +public class ActionServiceImplTransactionalTest extends TestCase +{ + private static ConfigurableApplicationContext ctx = + (ConfigurableApplicationContext)ApplicationContextHelper.getApplicationContext(); + + private StoreRef storeRef; + private NodeRef rootNodeRef; + + private NodeRef nodeRef; + private NodeRef folder; + private NodeService nodeService; + private ActionService actionService; + private TransactionService transactionService; + private RuntimeActionService runtimeActionService; + private RetryingTransactionHelper transactionHelper; + + @Override + protected void setUp() throws Exception { + this.transactionHelper = (RetryingTransactionHelper)ctx.getBean("retryingTransactionHelper"); + this.nodeService = (NodeService)ctx.getBean("nodeService"); + this.actionService = (ActionService)ctx.getBean("actionService"); + this.runtimeActionService = (RuntimeActionService)ctx.getBean("actionService"); + this.transactionService = (TransactionService)ctx.getBean("transactionService"); + + AuthenticationUtil.setRunAsUserSystem(); + + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + + // Where to put things + this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); + this.rootNodeRef = this.nodeService.getRootNode(this.storeRef); + + // 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(); + + txn.commit(); + + // Register the test executor, if needed + if(!ctx.containsBean(SleepActionExecuter.NAME)) + { + ctx.getBeanFactory().registerSingleton( + SleepActionExecuter.NAME, + new SleepActionExecuter() + ); + } + } + + /** + * Tests that when we run an action, either + * synchronously or asynchronously, with it + * working or failing, that the action execution + * service correctly sets the flags + */ + public void testExecutionTrackingOnExecution() throws Exception { + final SleepActionExecuter sleepActionExec = new SleepActionExecuter(); + sleepActionExec.setSleepMs(10); + Action action; + NodeRef actionNode; + + // We need real transactions + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + + + // =========================================================== + // Execute a transient Action that works, synchronously + // =========================================================== + action = createWorkingSleepAction(); + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + this.actionService.executeAction(action, this.nodeRef); + + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Completed, action.getExecutionStatus()); + + + // =========================================================== + // Execute a transient Action that fails, synchronously + // =========================================================== + action = createFailingMoveAction(); + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + try { + this.actionService.executeAction(action, this.nodeRef); + fail("Action should have failed, and the error been thrown"); + } catch(Exception e) {} + + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNotNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Failed, action.getExecutionStatus()); + + // Tidy up from the action failure + txn.rollback(); + txn = transactionService.getUserTransaction(); + txn.begin(); + + + // =========================================================== + // Execute a stored Action that works, synchronously + // =========================================================== + action = createWorkingSleepAction(); + this.actionService.saveAction(this.nodeRef, action); + actionNode = action.getNodeRef(); + assertNotNull(actionNode); + + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + this.actionService.executeAction(action, this.nodeRef); + + // Check our copy + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Completed, action.getExecutionStatus()); + + // Now re-load and check the stored one + action = runtimeActionService.createAction(actionNode); + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Completed, action.getExecutionStatus()); + + + // =========================================================== + // Execute a stored Action that fails, synchronously + // =========================================================== + action = createFailingMoveAction(); + this.actionService.saveAction(this.nodeRef, action); + actionNode = action.getNodeRef(); + String actionId = action.getId(); + assertNotNull(actionNode); + + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + // Save this + txn.commit(); + txn = transactionService.getUserTransaction(); + txn.begin(); + + // Run the action - will fail and trigger a rollback + try { + this.actionService.executeAction(action, this.nodeRef); + fail("Action should have failed, and the error been thrown"); + } catch(Exception e) {} + + // Check our copy + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNotNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Failed, action.getExecutionStatus()); + + // Wait for the post-rollback update to complete + // (The stored one gets updated asynchronously) + txn.rollback(); + Thread.sleep(100); + txn = transactionService.getUserTransaction(); + txn.begin(); + + // Now re-load and check the stored one + action = runtimeActionService.createAction(actionNode); + assertEquals(actionId, action.getId()); + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNotNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Failed, action.getExecutionStatus()); + + // Tidy up from the action failure + txn.commit(); + txn = transactionService.getUserTransaction(); + txn.begin(); + + + // =========================================================== + // Execute a transient Action that works, asynchronously + // =========================================================== + action = createWorkingSleepAction(); + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + this.actionService.executeAction(action, this.nodeRef, false, true); + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Pending, action.getExecutionStatus()); + + // End the transaction. Should allow the async action + // to be executed + txn.commit(); + Thread.sleep(100); + + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Completed, action.getExecutionStatus()); + + // Put things back ready for the next check + txn = transactionService.getUserTransaction(); + txn.begin(); + + + // =========================================================== + // Execute a transient Action that fails, asynchronously + // =========================================================== + action = createFailingMoveAction(); + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + this.actionService.executeAction(action, this.nodeRef, false, true); + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Pending, action.getExecutionStatus()); + + // End the transaction. Should allow the async action + // to be executed + txn.commit(); + Thread.sleep(100); + + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNotNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Failed, action.getExecutionStatus()); + + // Put things back ready for the next check + txn = transactionService.getUserTransaction(); + txn.begin(); + + + // =========================================================== + // Execute a stored Action that works, asynchronously + // =========================================================== + action = createWorkingSleepAction(); + this.actionService.saveAction(this.nodeRef, action); + actionNode = action.getNodeRef(); + assertNotNull(actionNode); + + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + this.actionService.executeAction(action, this.nodeRef, false, true); + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Pending, action.getExecutionStatus()); + + // End the transaction. Should allow the async action + // to be executed + txn.commit(); + Thread.sleep(100); + txn = transactionService.getUserTransaction(); + txn.begin(); + + // Check our copy + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Completed, action.getExecutionStatus()); + + // Now re-load and check the stored one + action = runtimeActionService.createAction(actionNode); + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Completed, action.getExecutionStatus()); + + + // =========================================================== + // Execute a stored Action that fails, asynchronously + // =========================================================== + action = createFailingMoveAction(); + this.actionService.saveAction(this.nodeRef, action); + actionNode = action.getNodeRef(); + assertNotNull(actionNode); + + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + this.actionService.executeAction(action, this.nodeRef, false, true); + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Pending, action.getExecutionStatus()); + + // End the transaction. Should allow the async action + // to be executed + // Need to wait longer, as we have two async actions + // that need to occur - action + record + txn.commit(); + Thread.sleep(250); + txn = transactionService.getUserTransaction(); + txn.begin(); + + // Check our copy + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNotNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Failed, action.getExecutionStatus()); + + // Now re-load and check the stored one + action = runtimeActionService.createAction(actionNode); + assertNotNull(action.getExecutionStartDate()); + assertNotNull(action.getExecutionEndDate()); + assertBefore(action.getExecutionStartDate(), action.getExecutionEndDate()); + assertBefore(action.getExecutionEndDate(), new Date()); + assertNotNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.Failed, action.getExecutionStatus()); + } + + private Action createFailingMoveAction() { + Action failingAction = this.actionService.createAction(MoveActionExecuter.NAME); + failingAction.setParameterValue(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN); + failingAction.setParameterValue(MoveActionExecuter.PARAM_ASSOC_QNAME, ContentModel.ASSOC_CHILDREN); + // Create a bad node ref + NodeRef badNodeRef = new NodeRef(this.storeRef, "123123"); + failingAction.setParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER, badNodeRef); + + return failingAction; + } + private Action createWorkingSleepAction() { + Action workingAction = actionService.createAction(SleepActionExecuter.NAME); + return workingAction; + } + + /** + * This class is only used during JUnit testing. + * + * @author Neil Mc Erlean + */ + public static class SleepActionFilter extends AbstractAsynchronousActionFilter + { + public int compare(OngoingAsyncAction sae1, OngoingAsyncAction sae2) + { + // Sleep actions are always equivalent. + return 0; + } + } + + /** + * This class is only intended for use in JUnit tests. + * + * @author Neil McErlean. + */ + public static class SleepActionExecuter extends ActionExecuterAbstractBase + { + public static final String NAME = "sleep-action"; + private int sleepMs; + + private int timesExecuted = 0; + private void incrementTimesExecutedCount() {timesExecuted++;} + public int getTimesExecuted() {return timesExecuted;} + + public int getSleepMs() + { + return sleepMs; + } + + public void setSleepMs(int sleepMs) + { + this.sleepMs = sleepMs; + } + + /** + * Add parameter definitions + */ + @Override + protected void addParameterDefinitions(List paramList) + { + // Intentionally empty + } + + @Override + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) { + try + { + Thread.sleep(sleepMs); + } + catch (InterruptedException ignored) + { + // Intentionally empty + } + finally + { + incrementTimesExecutedCount(); + } + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/action/ActionTestSuite.java b/source/java/org/alfresco/repo/action/ActionTestSuite.java index cc428f537e..f11b4f914f 100644 --- a/source/java/org/alfresco/repo/action/ActionTestSuite.java +++ b/source/java/org/alfresco/repo/action/ActionTestSuite.java @@ -69,6 +69,9 @@ public class ActionTestSuite extends TestSuite suite.addTestSuite(SpecialiseTypeActionExecuterTest.class); suite.addTestSuite(RemoveFeaturesActionExecuterTest.class); + // Tests which care about transactions + suite.addTestSuite(ActionServiceImplTransactionalTest.class); + return suite; } }