diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java index d9d2702aec..aaf13f378c 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java @@ -47,6 +47,8 @@ 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.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; @@ -1228,7 +1230,8 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest return createWorkingSleepAction(id, this.actionService); } protected static Action createWorkingSleepAction(String id, ActionService actionService) throws Exception { - Action workingAction = actionService.createAction(SleepActionExecuter.NAME); + Action workingAction = new CancellableSleepAction( + actionService.createAction(SleepActionExecuter.NAME)); if(id != null) { Field idF = ParameterizedItemImpl.class.getDeclaredField("id"); idF.setAccessible(true); @@ -1267,6 +1270,8 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest public int getTimesExecuted() {return timesExecuted;} private Thread executingThread; + private ActionTrackingService actionTrackingService; + /** * Loads this executor into the ApplicationContext, if it * isn't already there @@ -1275,9 +1280,14 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest { if(!ctx.containsBean(SleepActionExecuter.NAME)) { + // Create, and do dependencies + SleepActionExecuter executor = new SleepActionExecuter(); + executor.actionTrackingService = (ActionTrackingService) + ctx.getBean("actionTrackingService"); + // Register ctx.getBeanFactory().registerSingleton( SleepActionExecuter.NAME, - new SleepActionExecuter() + executor ); } } @@ -1329,8 +1339,21 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest { throw new RuntimeException("Bang!"); } + + CancellableSleepAction ca = (CancellableSleepAction)action; + boolean cancelled = actionTrackingService.isCancellationRequested(ca); + if(cancelled) + { + throw new RuntimeException("Cancelled!"); + } } } + protected static class CancellableSleepAction extends ActionImpl implements CancellableAction + { + public CancellableSleepAction(Action action) { + super(action); + } + } public static void assertBefore(Date before, Date after) { diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java index 2690c3e920..88ae8dab97 100644 --- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java @@ -65,6 +65,9 @@ public class ActionTrackingServiceImpl implements ActionTrackingService */ private short nextExecutionId = 1; + /** How we separate bits of the cache key */ + private static final char cacheKeyPartSeparator = '='; + /** * Set the transaction service * @@ -291,7 +294,8 @@ public class ActionTrackingServiceImpl implements ActionTrackingService public List getExecutingActions(Action action) { Collection actions = executingActionsCache.getKeys(); List details = new ArrayList(); - String match = action.getActionDefinitionName() + "-" + action.getId(); + String match = action.getActionDefinitionName() + cacheKeyPartSeparator + + action.getId() + cacheKeyPartSeparator; for(String key : actions) { if(key.startsWith(match)) { details.add( buildExecutionSummary(key) ); @@ -303,8 +307,9 @@ public class ActionTrackingServiceImpl implements ActionTrackingService public List getExecutingActions(String type) { Collection actions = executingActionsCache.getKeys(); List details = new ArrayList(); + String match = type + cacheKeyPartSeparator; for(String key : actions) { - if(key.startsWith(type)) { + if(key.startsWith(match)) { details.add( buildExecutionSummary(key) ); } } @@ -327,16 +332,16 @@ public class ActionTrackingServiceImpl implements ActionTrackingService protected static String generateCacheKey(Action action) { return - action.getActionDefinitionName() + "=" + - action.getId() + "=" + + action.getActionDefinitionName() + cacheKeyPartSeparator + + action.getId() + cacheKeyPartSeparator + "1"//action.getExecutionInstance // TODO ; } protected static String generateCacheKey(ExecutionSummary summary) { return - summary.getActionType() + "=" + - summary.getActionId() + "=" + + summary.getActionType() + cacheKeyPartSeparator + + summary.getActionId() + cacheKeyPartSeparator + summary.getExecutionInstance() ; } @@ -364,7 +369,7 @@ public class ActionTrackingServiceImpl implements ActionTrackingService */ protected static ExecutionSummary buildExecutionSummary(String key) { - StringTokenizer st = new StringTokenizer(key, "="); + StringTokenizer st = new StringTokenizer(key, new String(new char[]{cacheKeyPartSeparator})); String actionType = st.nextToken(); String actionId = st.nextToken(); int executionInstance = Integer.parseInt(st.nextToken()); diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java index 0ee6362849..7ee869eaf8 100644 --- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java @@ -27,6 +27,7 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionServiceImplTest.CancellableSleepAction; import org.alfresco.repo.action.ActionServiceImplTest.SleepActionExecuter; import org.alfresco.repo.action.executer.MoveActionExecuter; import org.alfresco.repo.cache.EhCacheAdapter; @@ -69,12 +70,10 @@ public class ActionTrackingServiceImplTest extends TestCase private TransactionService transactionService; private RuntimeActionService runtimeActionService; private ActionTrackingService actionTrackingService; - private RetryingTransactionHelper transactionHelper; private EhCacheAdapter executingActionsCache; @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"); @@ -109,6 +108,11 @@ public class ActionTrackingServiceImplTest extends TestCase txn.commit(); + // Cache should start empty each time + for(String key : executingActionsCache.getKeys()) { + executingActionsCache.remove(key); + } + // Register the test executor, if needed SleepActionExecuter.registerIfNeeded(ctx); } @@ -336,10 +340,203 @@ public class ActionTrackingServiceImplTest extends TestCase /** Ensure that the listing functions work */ public void testListings() throws Exception { - // TODO + // All listings start blank + assertEquals( + 0, actionTrackingService.getAllExecutingActions().size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions("test").size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions(SleepActionExecuter.NAME).size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions(createWorkingSleepAction(null)).size() + ); + + // Create some actions + Action sleepAction1 = createWorkingSleepAction("12345"); + Action sleepAction2 = createWorkingSleepAction("54321"); + Action moveAction = createFailingMoveAction(); + + // Start putting them in + actionTrackingService.recordActionExecuting(sleepAction1); + assertEquals( + 1, actionTrackingService.getAllExecutingActions().size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions("test").size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(SleepActionExecuter.NAME).size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(sleepAction1).size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions(sleepAction2).size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions(moveAction).size() + ); + + actionTrackingService.recordActionExecuting(moveAction); + assertEquals( + 2, actionTrackingService.getAllExecutingActions().size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions("test").size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(SleepActionExecuter.NAME).size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(sleepAction1).size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions(sleepAction2).size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(moveAction).size() + ); + + actionTrackingService.recordActionExecuting(sleepAction2); + assertEquals( + 3, actionTrackingService.getAllExecutingActions().size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions("test").size() + ); + assertEquals( + 2, actionTrackingService.getExecutingActions(SleepActionExecuter.NAME).size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(sleepAction1).size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(sleepAction2).size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(moveAction).size() + ); + + // Now have some finish, should leave the cache + actionTrackingService.recordActionComplete(sleepAction2); + assertEquals( + 2, actionTrackingService.getAllExecutingActions().size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions("test").size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(SleepActionExecuter.NAME).size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(sleepAction1).size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions(sleepAction2).size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(moveAction).size() + ); + + actionTrackingService.recordActionComplete(sleepAction1); + assertEquals( + 1, actionTrackingService.getAllExecutingActions().size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions("test").size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions(SleepActionExecuter.NAME).size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions(sleepAction1).size() + ); + assertEquals( + 0, actionTrackingService.getExecutingActions(sleepAction2).size() + ); + assertEquals( + 1, actionTrackingService.getExecutingActions(moveAction).size() + ); + + // TODO Multiple actions of the same instance } - // TODO Cancel related + /** Cancel related */ + public void testCancellation() throws Exception { + // Ensure we get the right answers checking + CancellableSleepAction sleepAction1 = (CancellableSleepAction)createWorkingSleepAction(null); + CancellableSleepAction sleepAction2 = (CancellableSleepAction)createWorkingSleepAction(null); + actionTrackingService.recordActionExecuting(sleepAction1); + actionTrackingService.recordActionExecuting(sleepAction2); + assertEquals(false, actionTrackingService.isCancellationRequested(sleepAction1)); + assertEquals(false, actionTrackingService.isCancellationRequested(sleepAction2)); + + // Cancel with the action + actionTrackingService.requestActionCancellation(sleepAction1); + assertEquals(true, actionTrackingService.isCancellationRequested(sleepAction1)); + assertEquals(false, actionTrackingService.isCancellationRequested(sleepAction2)); + + // Cancel with the summary + ExecutionSummary s2 = ActionTrackingServiceImpl.buildExecutionSummary(sleepAction2); + actionTrackingService.requestActionCancellation(s2); + assertEquals(true, actionTrackingService.isCancellationRequested(sleepAction1)); + assertEquals(true, actionTrackingService.isCancellationRequested(sleepAction2)); + + + // If the action had gone missing from the cache, + // then a check will put it back + CancellableSleepAction sleepAction3 = (CancellableSleepAction)createWorkingSleepAction(null); + String key3 = ActionTrackingServiceImpl.generateCacheKey(sleepAction3); + + assertNull(executingActionsCache.get(key3)); + assertEquals(false, actionTrackingService.isCancellationRequested(sleepAction3)); + assertNotNull(executingActionsCache.get(key3)); + + executingActionsCache.remove(key3); + assertNull(executingActionsCache.get(key3)); + assertEquals(false, actionTrackingService.isCancellationRequested(sleepAction3)); + assertNotNull(executingActionsCache.get(key3)); + + actionTrackingService.requestActionCancellation(sleepAction3); + assertEquals(true, actionTrackingService.isCancellationRequested(sleepAction3)); + assertNotNull(executingActionsCache.get(key3)); + + + // Now have one execute and cancel it, ensure it does + final SleepActionExecuter sleepActionExec = + (SleepActionExecuter)ctx.getBean(SleepActionExecuter.NAME); + sleepActionExec.setSleepMs(10000); + + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + + executingActionsCache.remove(key3); + this.actionService.executeAction(sleepAction3, this.nodeRef, false, true); + + // End the transaction. Should allow the async action + // to be started + txn.commit(); + Thread.sleep(150); + + assertEquals(false, actionTrackingService.isCancellationRequested(sleepAction3)); + assertNotNull(executingActionsCache.get(key3)); + + actionTrackingService.requestActionCancellation(sleepAction3); + + assertEquals(true, actionTrackingService.isCancellationRequested(sleepAction3)); + assertNotNull(executingActionsCache.get(key3)); + + // Have it finish sleeping, will have been cancelled + sleepActionExec.getExecutingThread().interrupt(); + Thread.sleep(100); + + // TODO Proper cancelled exception and tracking + assertEquals(ActionStatus.Failed, sleepAction3.getExecutionStatus()); + assertEquals("Cancelled!", sleepAction3.getExecutionFailureMessage()); + } // =================================================================== // @@ -673,5 +870,4 @@ public class ActionTrackingServiceImplTest extends TestCase private Action createWorkingSleepAction(String id) throws Exception { return ActionServiceImplTest.createWorkingSleepAction(id, actionService); } - } \ No newline at end of file