diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTransactionalTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTransactionalTest.java
index 10f90c8e1a..afd6b437db 100644
--- a/source/java/org/alfresco/repo/action/ActionServiceImplTransactionalTest.java
+++ b/source/java/org/alfresco/repo/action/ActionServiceImplTransactionalTest.java
@@ -28,6 +28,7 @@ import javax.transaction.UserTransaction;
import junit.framework.TestCase;
import org.alfresco.model.ContentModel;
+import org.alfresco.repo.action.ActionTrackingServiceImplTest.SleepActionExecuter;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.repo.action.executer.MoveActionExecuter;
import org.alfresco.repo.content.MimetypeMap;
@@ -120,7 +121,8 @@ public class ActionServiceImplTransactionalTest extends TestCase
* service correctly sets the flags
*/
public void testExecutionTrackingOnExecution() throws Exception {
- final SleepActionExecuter sleepActionExec = new SleepActionExecuter();
+ final SleepActionExecuter sleepActionExec =
+ (SleepActionExecuter)ctx.getBean(SleepActionExecuter.NAME);
sleepActionExec.setSleepMs(10);
Action action;
NodeRef actionNode;
diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java
index d8ebf4802b..2690c3e920 100644
--- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java
+++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImpl.java
@@ -103,6 +103,11 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
public void recordActionComplete(Action action)
{
+ if (logger.isDebugEnabled() == true)
+ {
+ logger.debug("Action " + action + " has completed execution");
+ }
+
// Mark it as having worked
((ActionImpl)action).setExecutionEndDate(new Date());
((ActionImpl)action).setExecutionStatus(ActionStatus.Completed);
@@ -119,13 +124,23 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
public void recordActionExecuting(Action action)
{
+ if (logger.isDebugEnabled() == true)
+ {
+ logger.debug("Action " + action + " has begun exection");
+ }
+
// Mark the action as starting
((ActionImpl)action).setExecutionStartDate(new Date());
((ActionImpl)action).setExecutionStatus(ActionStatus.Running);
// TODO assign it a (unique) execution ID
+ // (Keep checking to see if the key is used as we
+ // increase nextExecutionId until it isn't)
+ String key = generateCacheKey(action);
- // TODO Put it into the cache
+ // Put it into the cache
+ ExecutionDetails details = buildExecutionDetails(action);
+ executingActionsCache.put(key, details);
}
/**
@@ -219,7 +234,8 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
"Your running actions cache is probably too small"
);
- // TODO Re-generate
+ // Re-generate
+ details = buildExecutionDetails(action);
// Re-save into the cache, so it's there for
// next time
@@ -308,34 +324,59 @@ public class ActionTrackingServiceImpl implements ActionTrackingService
/**
* Generates the cache key for the specified action.
*/
- protected String generateCacheKey(Action action)
+ protected static String generateCacheKey(Action action)
{
return
- action.getActionDefinitionName() + "-" +
- action.getId() + "-" +
- ""//action.getExecutionInstance // TODO
+ action.getActionDefinitionName() + "=" +
+ action.getId() + "=" +
+ "1"//action.getExecutionInstance // TODO
;
}
- protected String generateCacheKey(ExecutionSummary summary)
+ protected static String generateCacheKey(ExecutionSummary summary)
{
return
- summary.getActionType() + "-" +
- summary.getActionId() + "-" +
+ summary.getActionType() + "=" +
+ summary.getActionId() + "=" +
summary.getExecutionInstance()
;
}
+
+ /**
+ * Builds up the details to be stored in a cache
+ * for a specific action
+ */
+ protected static ExecutionDetails buildExecutionDetails(Action action)
+ {
+ // TODO Where are we?
+ String machine = "TODO";
+
+ // Generate
+ return new ExecutionDetails(
+ buildExecutionSummary(action),
+ action.getNodeRef(), machine,
+ action.getExecutionStartDate(), false
+ );
+ }
/**
* Turns a cache key back into its constituent
* parts, for easier access.
*/
- protected ExecutionSummary buildExecutionSummary(String key)
+ protected static ExecutionSummary buildExecutionSummary(String key)
{
- StringTokenizer st = new StringTokenizer(key, "-");
+ StringTokenizer st = new StringTokenizer(key, "=");
String actionType = st.nextToken();
String actionId = st.nextToken();
int executionInstance = Integer.parseInt(st.nextToken());
return new ExecutionSummary(actionType, actionId, executionInstance);
}
+ protected static ExecutionSummary buildExecutionSummary(Action action)
+ {
+ return new ExecutionSummary(
+ action.getActionDefinitionName(),
+ action.getId(),
+ 1 // TODO
+ );
+ }
}
diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java
new file mode 100644
index 0000000000..1fed712479
--- /dev/null
+++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java
@@ -0,0 +1,389 @@
+/*
+ * 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.lang.reflect.Field;
+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.ActionServiceImplTransactionalTest.SleepActionExecuter;
+import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
+import org.alfresco.repo.action.executer.MoveActionExecuter;
+import org.alfresco.repo.cache.EhCacheAdapter;
+import org.alfresco.repo.content.MimetypeMap;
+import org.alfresco.repo.search.impl.parsers.CMISParser.nullPredicate_return;
+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.ActionTrackingService;
+import org.alfresco.service.cmr.action.ExecutionDetails;
+import org.alfresco.service.cmr.action.ExecutionSummary;
+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 tracking service tests. These mostly need
+ * careful control over the transactions they use.
+ *
+ * @author Nick Burch
+ */
+public class ActionTrackingServiceImplTest 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 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");
+ this.actionTrackingService = (ActionTrackingService)ctx.getBean("actionTrackingService");
+ this.transactionService = (TransactionService)ctx.getBean("transactionService");
+ this.executingActionsCache = (EhCacheAdapter)ctx.getBean("executingActionsSharedCache");
+
+ 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()
+ );
+ }
+ }
+
+ /** Creating cache keys */
+ public void testCreateCacheKeys() throws Exception
+ {
+ Action action = createWorkingSleepAction("1234");
+ assertEquals("sleep-action", action.getActionDefinitionName());
+ assertEquals("1234", action.getId());
+ // assertNull(action.getExecutionInstance()); // TODO
+
+ // From an action
+ String key = ActionTrackingServiceImpl.generateCacheKey(action);
+ assertEquals("sleep-action=1234=1", key);
+
+ // From an ExecutionSummary
+ ExecutionSummary s = new ExecutionSummary("sleep-action", "1234", 1);
+ key = ActionTrackingServiceImpl.generateCacheKey(s);
+ assertEquals("sleep-action=1234=1", key);
+ }
+
+ /** Creating ExecutionDetails and ExecutionSummary */
+ public void testExecutionDetailsSummary() throws Exception
+ {
+ // Create the ExecutionSummary from an action
+ Action action = createWorkingSleepAction("1234");
+ String key = ActionTrackingServiceImpl.generateCacheKey(action);
+
+ ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action);
+ assertEquals("sleep-action", s.getActionType());
+ assertEquals("1234", s.getActionId());
+ assertEquals(1, s.getExecutionInstance());
+
+ // Create the ExecutionSummery from a key
+ s = ActionTrackingServiceImpl.buildExecutionSummary(key);
+ assertEquals("sleep-action", s.getActionType());
+ assertEquals("1234", s.getActionId());
+ assertEquals(1, s.getExecutionInstance());
+
+ // Now create ExecutionDetails
+ ExecutionDetails d = ActionTrackingServiceImpl.buildExecutionDetails(action);
+ assertNotNull(d.getExecutionSummary());
+ assertEquals("sleep-action", d.getActionType());
+ assertEquals("1234", d.getActionId());
+ assertEquals(1, d.getExecutionInstance());
+ assertEquals(null, d.getPersistedActionRef());
+ assertEquals(null, d.getStartedAt());
+ }
+
+ // Running an action gives it an execution ID
+ // TODO
+
+ /**
+ * The correct things happen with the cache
+ * when you mark things as working / failed / etc
+ */
+ public void testInOutCache() throws Exception
+ {
+ Action action = createWorkingSleepAction("1234");
+ assertEquals(ActionStatus.New, action.getExecutionStatus());
+
+ String key = ActionTrackingServiceImpl.generateCacheKey(action);
+ assertEquals(null, executingActionsCache.get(key));
+
+
+ // Can complete or fail, won't be there
+ actionTrackingService.recordActionComplete(action);
+ assertEquals(ActionStatus.Completed, action.getExecutionStatus());
+ assertEquals(null, executingActionsCache.get(key));
+
+ actionTrackingService.recordActionFailure(action, new Exception("Testing"));
+ assertEquals(ActionStatus.Failed, action.getExecutionStatus());
+ assertEquals("Testing", action.getExecutionFailureMessage());
+ assertEquals(null, executingActionsCache.get(key));
+
+
+ // Pending won't add it in either
+ actionTrackingService.recordActionPending(action);
+ assertEquals(ActionStatus.Pending, action.getExecutionStatus());
+ assertEquals(null, executingActionsCache.get(key));
+
+
+ // Run it, will go in
+ actionTrackingService.recordActionExecuting(action);
+ assertEquals(ActionStatus.Running, action.getExecutionStatus());
+ assertNotNull(null, executingActionsCache.get(key));
+
+ ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action);
+ ExecutionDetails d = actionTrackingService.getExecutionDetails(s);
+ assertNotNull(d.getExecutionSummary());
+ assertEquals("sleep-action", d.getActionType());
+ assertEquals("1234", d.getActionId());
+ assertEquals(1, d.getExecutionInstance());
+ assertEquals(null, d.getPersistedActionRef());
+ assertNotNull(null, d.getStartedAt());
+
+
+ // Completion removes it
+ actionTrackingService.recordActionComplete(action);
+ assertEquals(ActionStatus.Completed, action.getExecutionStatus());
+ assertEquals(null, executingActionsCache.get(key));
+
+ // Failure removes it
+ actionTrackingService.recordActionExecuting(action);
+ assertNotNull(null, executingActionsCache.get(key));
+
+ actionTrackingService.recordActionFailure(action, new Exception("Testing"));
+ assertEquals(ActionStatus.Failed, action.getExecutionStatus());
+ assertEquals("Testing", action.getExecutionFailureMessage());
+ assertEquals(null, executingActionsCache.get(key));
+ }
+
+ /** Working actions go into the cache, then out */
+ public void testWorkingActions() throws Exception
+ {
+ final SleepActionExecuter sleepActionExec =
+ (SleepActionExecuter)ctx.getBean(SleepActionExecuter.NAME);
+ sleepActionExec.setSleepMs(10000);
+
+ // Have it run asynchronously
+ UserTransaction txn = transactionService.getUserTransaction();
+ txn.begin();
+ Action action = createWorkingSleepAction("54321");
+ assertNull(action.getExecutionStartDate());
+ assertNull(action.getExecutionEndDate());
+ assertNull(action.getExecutionFailureMessage());
+ assertEquals(ActionStatus.New, action.getExecutionStatus());
+
+ String key = ActionTrackingServiceImpl.generateCacheKey(action);
+ assertEquals(null, executingActionsCache.get(key));
+
+ this.actionService.executeAction(action, this.nodeRef, false, true);
+
+
+ // End the transaction. Should allow the async action
+ // to be started
+ txn.commit();
+ Thread.sleep(150);
+
+
+ // Check it's in the cache
+ System.out.println("Checking the cache for " + key);
+ assertNotNull(executingActionsCache.get(key));
+
+ ExecutionSummary s = ActionTrackingServiceImpl.buildExecutionSummary(action);
+ ExecutionDetails d = actionTrackingService.getExecutionDetails(s);
+ assertNotNull(d.getExecutionSummary());
+ assertEquals("sleep-action", d.getActionType());
+ assertEquals("54321", d.getActionId());
+ assertEquals(1, d.getExecutionInstance());
+ assertEquals(null, d.getPersistedActionRef());
+ assertNotNull(null, d.getStartedAt());
+
+
+ // Tell it to stop sleeping
+ sleepActionExec.executingThread.interrupt();
+ Thread.sleep(100);
+
+
+ // Ensure it went away again
+ assertEquals(ActionStatus.Completed, action.getExecutionStatus());
+ assertEquals(null, executingActionsCache.get(key));
+
+ d = actionTrackingService.getExecutionDetails(s);
+ assertEquals(null, d);
+ }
+
+ /** Failing actions go into the cache, then out */
+ public void testFailingActions() throws Exception
+ {
+
+ }
+
+ // Ensure that the listing functions work
+ // TODO
+
+
+ // =================================================================== //
+
+
+ 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(String id) throws Exception {
+ Action workingAction = actionService.createAction(SleepActionExecuter.NAME);
+ Field idF = ParameterizedItemImpl.class.getDeclaredField("id");
+ idF.setAccessible(true);
+ idF.set(workingAction, id);
+ 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;}
+ private Thread executingThread;
+
+ 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) {
+ executingThread = Thread.currentThread();
+ //System.err.println("Sleeping for " + sleepMs + " for " + action);
+
+ try
+ {
+ Thread.sleep(sleepMs);
+ }
+ catch (InterruptedException ignored)
+ {
+ // Intentionally empty
+ }
+ finally
+ {
+ incrementTimesExecutedCount();
+ }
+ }
+ }
+}
\ No newline at end of file