Have the action service record when actions run, and if they worked

Handles persisting this action data for sucessful actions
(Failure information persistance still to follow)
Includes lots of unit tests for working and failing sync and async actions


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@21175 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Nick Burch
2010-07-14 16:08:37 +00:00
parent a7d55e7fe4
commit 7c46ccea69
5 changed files with 356 additions and 17 deletions

View File

@@ -618,7 +618,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
logger.debug(builder.toString()); logger.debug(builder.toString());
logger.debug("Current action = " + action.getId()); logger.debug("Current action = " + action.getId());
} }
// get the current user early in case the process fails and we are // get the current user early in case the process fails and we are
// unable to do it later // unable to do it later
String currentUserName = this.authenticationContext.getCurrentUserName(); String currentUserName = this.authenticationContext.getCurrentUserName();
@@ -655,8 +655,21 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
// Check and execute now // Check and execute now
if (checkConditions == false || evaluateAction(action, actionedUponNodeRef) == true) if (checkConditions == false || evaluateAction(action, actionedUponNodeRef) == true)
{ {
// Mark the action as starting
((ActionImpl)action).setExecutionStartDate(new Date());
((ActionImpl)action).setExecutionStatus(ActionStatus.Running);
// Execute the action // Execute the action
directActionExecution(action, actionedUponNodeRef); directActionExecution(action, actionedUponNodeRef);
// Mark it as having worked
((ActionImpl)action).setExecutionEndDate(new Date());
((ActionImpl)action).setExecutionStatus(ActionStatus.Completed);
((ActionImpl)action).setExecutionFailureMessage(null);
if(action.getNodeRef() != null)
{
saveActionImpl(action.getNodeRef(), action);
}
} }
} }
finally finally
@@ -693,6 +706,9 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
queueAction(compensatingAction, actionedUponNodeRef); queueAction(compensatingAction, actionedUponNodeRef);
} }
} }
// Have the failure logged on the action
recordActionFailure(action, exception);
// Rethrow the exception // Rethrow the exception
if (exception instanceof RuntimeException) if (exception instanceof RuntimeException)
@@ -707,6 +723,27 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
} }
} }
} }
/**
* Schedule the recording of the action failure to occur
* in another transaction
*/
protected void recordActionFailure(Action action, Throwable exception)
{
if (logger.isDebugEnabled() == true)
{
logger.debug("Recording failure of action " + action + " due to " + exception.getMessage());
}
((ActionImpl)action).setExecutionEndDate(new Date());
((ActionImpl)action).setExecutionStatus(ActionStatus.Failed);
((ActionImpl)action).setExecutionFailureMessage(exception.getMessage());
if(action.getNodeRef() != null)
{
// TODO
}
}
/** /**
* @see org.alfresco.repo.action.RuntimeActionService#directActionExecution(org.alfresco.service.cmr.action.Action, * @see org.alfresco.repo.action.RuntimeActionService#directActionExecution(org.alfresco.service.cmr.action.Action,
@@ -1531,6 +1568,7 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A
if (pendingActions.contains(pendingAction) == false) if (pendingActions.contains(pendingAction) == false)
{ {
pendingActions.add(pendingAction); pendingActions.add(pendingAction);
((ActionImpl)action).setExecutionStatus(ActionStatus.Pending);
} }
} }
} }

View File

@@ -68,6 +68,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
private NodeRef nodeRef; private NodeRef nodeRef;
private NodeRef folder; private NodeRef folder;
private RuntimeActionService runtimeActionService;
private RetryingTransactionHelper transactionHelper; private RetryingTransactionHelper transactionHelper;
// @Override // @Override
@@ -89,6 +90,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
super.onSetUpInTransaction(); super.onSetUpInTransaction();
this.transactionHelper = (RetryingTransactionHelper)this.applicationContext.getBean("retryingTransactionHelper"); this.transactionHelper = (RetryingTransactionHelper)this.applicationContext.getBean("retryingTransactionHelper");
this.runtimeActionService = (RuntimeActionService)this.applicationContext.getBean("actionService");
// Create the node used for tests // Create the node used for tests
this.nodeRef = this.nodeService.createNode( this.nodeRef = this.nodeService.createNode(
@@ -106,6 +108,14 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
QName.createQName("{test}testFolder"), QName.createQName("{test}testFolder"),
ContentModel.TYPE_FOLDER).getChildRef(); ContentModel.TYPE_FOLDER).getChildRef();
// Register the test executor, if needed
if(!applicationContext.containsBean(SleepActionExecuter.NAME))
{
applicationContext.getBeanFactory().registerSingleton(
SleepActionExecuter.NAME,
new SleepActionExecuter()
);
}
} }
/** /**
@@ -1029,12 +1039,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
public void testSyncFailureBehaviour() public void testSyncFailureBehaviour()
{ {
// Create an action that is going to fail // Create an action that is going to fail
Action action = this.actionService.createAction(MoveActionExecuter.NAME); Action action = createFailingMoveAction();
action.setParameterValue(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN);
action.setParameterValue(MoveActionExecuter.PARAM_ASSOC_QNAME, ContentModel.ASSOC_CHILDREN);
// Create a bad node ref
NodeRef badNodeRef = new NodeRef(this.storeRef, "123123");
action.setParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER, badNodeRef);
try try
{ {
@@ -1079,12 +1084,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
public void testCompensatingAction() public void testCompensatingAction()
{ {
// Create an action that is going to fail // Create an action that is going to fail
final Action action = this.actionService.createAction(MoveActionExecuter.NAME); final Action action = createFailingMoveAction();
action.setParameterValue(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CHILDREN);
action.setParameterValue(MoveActionExecuter.PARAM_ASSOC_QNAME, ContentModel.ASSOC_CHILDREN);
// Create a bad node ref
NodeRef badNodeRef = new NodeRef(this.storeRef, "123123");
action.setParameterValue(MoveActionExecuter.PARAM_DESTINATION_FOLDER, badNodeRef);
action.setTitle("title"); action.setTitle("title");
// Create the compensating action // Create the compensating action
@@ -1212,8 +1212,300 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
* working or failing, that the action execution * working or failing, that the action execution
* service correctly sets the flags * service correctly sets the flags
*/ */
public void testExecutionTrackingOnExecution() { public void testExecutionTrackingOnExecution() throws Exception {
// TODO 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() {
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;
} }
/** /**
@@ -1279,4 +1571,12 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
} }
} }
} }
public static void assertBefore(Date before, Date after)
{
assertTrue(
before.toString() + " not before " + after.toString(),
before.getTime() <= after.getTime()
);
}
} }

View File

@@ -41,6 +41,7 @@ import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionServiceException; import org.alfresco.service.cmr.action.ActionServiceException;
import org.alfresco.service.cmr.action.ActionStatus;
import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
@@ -304,7 +305,7 @@ public class AsynchronousActionExecutionQueueImpl implements AsynchronousActionE
msg.append("Executing action ").append(action); msg.append("Executing action ").append(action);
logger.debug(msg.toString()); logger.debug(msg.toString());
} }
// Queue it and do it. // Queue it and do it.
ongoingActions.add(nodeBeingNewlyActioned); ongoingActions.add(nodeBeingNewlyActioned);
threadPoolExecutor.execute(runnable); threadPoolExecutor.execute(runnable);

View File

@@ -285,7 +285,6 @@ public class ReplicationServiceIntegrationTest extends BaseAlfrescoSpringTest
* Ensures that we can create, save, edit, save * Ensures that we can create, save, edit, save
* load, edit, save, load etc, all without * load, edit, save, load etc, all without
* problems, and without creating duplicates * problems, and without creating duplicates
* DISABLED until Derek can look at it
*/ */
public void testEditing() throws Exception public void testEditing() throws Exception
{ {

View File

@@ -23,6 +23,7 @@ import java.io.Serializable;
/** /**
* Action execution status enumeration * Action execution status enumeration
* *
* @deprecated Use {@link ActionStatus} instead
* @author Roy Wetherall * @author Roy Wetherall
*/ */
public enum ActionExecutionStatus implements Serializable public enum ActionExecutionStatus implements Serializable