diff --git a/source/java/org/alfresco/repo/action/ActionCancelledException.java b/source/java/org/alfresco/repo/action/ActionCancelledException.java
new file mode 100644
index 0000000000..0f2964ba1c
--- /dev/null
+++ b/source/java/org/alfresco/repo/action/ActionCancelledException.java
@@ -0,0 +1,35 @@
+/*
+ * 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 org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.service.cmr.action.CancellableAction;
+
+/**
+ * The Exception thrown when a {@link CancellableAction} detects
+ * that a cancel was requested, and needs the transaction it
+ * is in to be wound back as part of the cancellation.
+ */
+public class ActionCancelledException extends AlfrescoRuntimeException
+{
+ public ActionCancelledException(CancellableAction action)
+ {
+ super(action.toString());
+ }
+}
diff --git a/source/java/org/alfresco/repo/action/ActionImpl.java b/source/java/org/alfresco/repo/action/ActionImpl.java
index 1ba9ee62d9..5f8bcd953a 100644
--- a/source/java/org/alfresco/repo/action/ActionImpl.java
+++ b/source/java/org/alfresco/repo/action/ActionImpl.java
@@ -106,6 +106,16 @@ public class ActionImpl extends ParameterizedItemImpl implements Action
*/
private List actionConditions = new ArrayList();
+ /**
+ * When there is more than one instance of the
+ * action executing, both with the same ID,
+ * which one is this?
+ * This crops up most often with persisted
+ * actions, with two copies running, one on
+ * each of two different target nodes.
+ */
+ private int executionInstance = -1;
+
/**
* When the action started executing,
* or null if it hasn't yet.
@@ -466,6 +476,27 @@ public class ActionImpl extends ParameterizedItemImpl implements Action
getParameterValues().putAll(values);
}
+ /**
+ * When there is more than one instance of the
+ * action executing, both with the same ID,
+ * which one is this?
+ * This crops up most often with persisted
+ * actions, with two copies running, one on
+ * each of two different target nodes.
+ */
+ public int getExecutionInstance()
+ {
+ return executionInstance;
+ }
+
+ /**
+ * Called by the ActionService when the action
+ * begins running.
+ */
+ public void setExecutionInstance(int instance) {
+ executionInstance = instance;
+ }
+
public Date getExecutionStartDate() {
return executionStartDate;
}
diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java
index aaf13f378c..10b60078d1 100644
--- a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java
+++ b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java
@@ -795,6 +795,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
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 ++)
@@ -883,7 +884,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
postAsyncActionTest(
this.transactionService,
- 1000,
+ 1000l,
10,
new AsyncTest()
{
@@ -1344,7 +1345,7 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest
boolean cancelled = actionTrackingService.isCancellationRequested(ca);
if(cancelled)
{
- throw new RuntimeException("Cancelled!");
+ throw new ActionCancelledException(ca);
}
}
}
diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java
index 88ae8dab97..f9a7b01b93 100644
--- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java
+++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java
@@ -96,7 +96,12 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
{
this.executingActionsCache = executingActionsCache;
}
-
+
+
+ /** Used by unit tests only */
+ protected void resetNextExecutionId() {
+ this.nextExecutionId = 1;
+ }
public void recordActionPending(Action action)
@@ -139,6 +144,7 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
// TODO assign it a (unique) execution ID
// (Keep checking to see if the key is used as we
// increase nextExecutionId until it isn't)
+ ((ActionImpl)action).setExecutionInstance(nextExecutionId++); // TODO
String key = generateCacheKey(action);
// Put it into the cache
@@ -154,12 +160,30 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
{
if (logger.isDebugEnabled() == true)
{
- logger.debug("Will shortly record failure of action " + action + " due to " + exception.getMessage());
+ if(exception instanceof ActionCancelledException)
+ {
+ logger.debug("Will shortly record completed cancellation of action " + action);
+ }
+ else
+ {
+ logger.debug("Will shortly record failure of action " + action + " due to " + exception.getMessage());
+ }
}
+ // Record when it finished
((ActionImpl)action).setExecutionEndDate(new Date());
- ((ActionImpl)action).setExecutionStatus(ActionStatus.Failed);
- ((ActionImpl)action).setExecutionFailureMessage(exception.getMessage());
+
+ // Record it as Failed or Cancelled, depending on the exception
+ if(exception instanceof ActionCancelledException)
+ {
+ ((ActionImpl)action).setExecutionStatus(ActionStatus.Cancelled);
+ ((ActionImpl)action).setExecutionFailureMessage(null);
+ }
+ else
+ {
+ ((ActionImpl)action).setExecutionStatus(ActionStatus.Failed);
+ ((ActionImpl)action).setExecutionFailureMessage(exception.getMessage());
+ }
// Remove it from the cache, as it's no longer running
String key = generateCacheKey(action);
@@ -334,7 +358,7 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
return
action.getActionDefinitionName() + cacheKeyPartSeparator +
action.getId() + cacheKeyPartSeparator +
- "1"//action.getExecutionInstance // TODO
+ ((ActionImpl)action).getExecutionInstance()
;
}
protected static String generateCacheKey(ExecutionSummary summary)
@@ -381,7 +405,7 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
return new ExecutionSummary(
action.getActionDefinitionName(),
action.getId(),
- 1 // TODO
+ ((ActionImpl)action).getExecutionInstance()
);
}
}
diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java
index 7ee869eaf8..a1c4e634cf 100644
--- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java
+++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java
@@ -113,6 +113,10 @@ public class ActionTrackingServiceImplTest extends TestCase
executingActionsCache.remove(key);
}
+ // Reset the execution instance IDs, so we
+ // can predict what they'll be
+ ((ActionTrackingServiceImpl)actionTrackingService).resetNextExecutionId();
+
// Register the test executor, if needed
SleepActionExecuter.registerIfNeeded(ctx);
}
@@ -120,10 +124,13 @@ public class ActionTrackingServiceImplTest extends TestCase
/** Creating cache keys */
public void testCreateCacheKeys() throws Exception
{
- Action action = createWorkingSleepAction("1234");
+ ActionImpl action = (ActionImpl)createWorkingSleepAction("1234");
assertEquals("sleep-action", action.getActionDefinitionName());
assertEquals("1234", action.getId());
- // assertNull(action.getExecutionInstance()); // TODO
+ assertEquals(-1, action.getExecutionInstance());
+
+ // Give it a predictable execution instance
+ action.setExecutionInstance(1);
// From an action
String key = ActionTrackingServiceImpl.generateCacheKey(action);
@@ -138,8 +145,11 @@ public class ActionTrackingServiceImplTest extends TestCase
/** Creating ExecutionDetails and ExecutionSummary */
public void testExecutionDetailsSummary() throws Exception
{
- // Create the ExecutionSummary from an action
+ // Create an action with a known execution instance
Action action = createWorkingSleepAction("1234");
+ ((ActionImpl)action).setExecutionInstance(1);
+
+ // Create the ExecutionSummary from an action
String key = ActionTrackingServiceImpl.generateCacheKey(action);
ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action);
@@ -161,10 +171,15 @@ public class ActionTrackingServiceImplTest extends TestCase
assertEquals(1, d.getExecutionInstance());
assertEquals(null, d.getPersistedActionRef());
assertEquals(null, d.getStartedAt());
+
+ // TODO Check machine details
}
- // Running an action gives it an execution ID
- // TODO
+ /** Running an action gives it an execution ID */
+ public void testExecutionInstanceAssignment()
+ {
+ // TODO
+ }
/**
* The correct things happen with the cache
@@ -181,10 +196,12 @@ public class ActionTrackingServiceImplTest extends TestCase
// Can complete or fail, won't be there
actionTrackingService.recordActionComplete(action);
+ key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Completed, action.getExecutionStatus());
assertEquals(null, executingActionsCache.get(key));
actionTrackingService.recordActionFailure(action, new Exception("Testing"));
+ key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Failed, action.getExecutionStatus());
assertEquals("Testing", action.getExecutionFailureMessage());
assertEquals(null, executingActionsCache.get(key));
@@ -192,12 +209,14 @@ public class ActionTrackingServiceImplTest extends TestCase
// Pending won't add it in either
actionTrackingService.recordActionPending(action);
+ key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Pending, action.getExecutionStatus());
assertEquals(null, executingActionsCache.get(key));
- // Run it, will go in
+ // Run it, will go into the cache
actionTrackingService.recordActionExecuting(action);
+ key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Running, action.getExecutionStatus());
assertNotNull(null, executingActionsCache.get(key));
@@ -213,14 +232,17 @@ public class ActionTrackingServiceImplTest extends TestCase
// Completion removes it
actionTrackingService.recordActionComplete(action);
+ key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Completed, action.getExecutionStatus());
assertEquals(null, executingActionsCache.get(key));
// Failure removes it
actionTrackingService.recordActionExecuting(action);
+ key = ActionTrackingServiceImpl.generateCacheKey(action);
assertNotNull(null, executingActionsCache.get(key));
actionTrackingService.recordActionFailure(action, new Exception("Testing"));
+ key = ActionTrackingServiceImpl.generateCacheKey(action);
assertEquals(ActionStatus.Failed, action.getExecutionStatus());
assertEquals("Testing", action.getExecutionFailureMessage());
assertEquals(null, executingActionsCache.get(key));
@@ -252,6 +274,10 @@ public class ActionTrackingServiceImplTest extends TestCase
// to be started
txn.commit();
Thread.sleep(150);
+
+
+ // Will get an execution instance id, so a new key
+ key = ActionTrackingServiceImpl.generateCacheKey(action);
// Check it's in the cache
@@ -309,6 +335,10 @@ public class ActionTrackingServiceImplTest extends TestCase
Thread.sleep(150);
+ // Will get an execution instance id, so a new key
+ key = ActionTrackingServiceImpl.generateCacheKey(action);
+
+
// Check it's in the cache
System.out.println("Checking the cache for " + key);
assertNotNull(executingActionsCache.get(key));
@@ -521,6 +551,9 @@ public class ActionTrackingServiceImplTest extends TestCase
txn.commit();
Thread.sleep(150);
+ // Get the updated key, and check
+ key3 = ActionTrackingServiceImpl.generateCacheKey(sleepAction3);
+
assertEquals(false, actionTrackingService.isCancellationRequested(sleepAction3));
assertNotNull(executingActionsCache.get(key3));
@@ -531,11 +564,11 @@ public class ActionTrackingServiceImplTest extends TestCase
// Have it finish sleeping, will have been cancelled
sleepActionExec.getExecutingThread().interrupt();
- Thread.sleep(100);
+ Thread.sleep(150);
- // TODO Proper cancelled exception and tracking
- assertEquals(ActionStatus.Failed, sleepAction3.getExecutionStatus());
- assertEquals("Cancelled!", sleepAction3.getExecutionFailureMessage());
+ // Ensure the proper cancelled tracking
+ assertEquals(ActionStatus.Cancelled, sleepAction3.getExecutionStatus());
+ assertEquals(null, sleepAction3.getExecutionFailureMessage());
}
diff --git a/source/java/org/alfresco/service/cmr/action/CancellableAction.java b/source/java/org/alfresco/service/cmr/action/CancellableAction.java
index 9890bf5a1e..95314c23e0 100644
--- a/source/java/org/alfresco/service/cmr/action/CancellableAction.java
+++ b/source/java/org/alfresco/service/cmr/action/CancellableAction.java
@@ -18,11 +18,23 @@
*/
package org.alfresco.service.cmr.action;
+import org.alfresco.repo.action.ActionCancelledException;
+import org.alfresco.repo.action.executer.ActionExecuter;
+
/**
* A marker interface that forms part of the Cancel Action contract.
* An action that implements this interface commits to periodically
- * asking the {@link ActionService} if a cancel of it has been
- * requested, and orderly terminating itself if so.
+ * asking the {@link ActionTrackingService} if a cancel of it has
+ * been requested, and orderly terminating itself if so.
+ *
+ * Actions implementing this should, via their
+ * {@link ActionExecuter}, periodically call
+ * {@link ActionTrackingService#isCancellationRequested(CancellableAction)}
+ * to check if a cancel has been requested for them.
+ * If it has, they should tidy up as much as possible, and then throw
+ * a {@link ActionCancelledException} to indicate to the
+ * {@link ActionService} that they ceased running due to a
+ * cancel.
*
* @author Nick Burch
*/