From 6f828a8b97aa785ff78692b1aec4994786b38939 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Tue, 20 Jul 2010 17:00:29 +0000 Subject: [PATCH] Action Tracking Service work (Replication Task 79) - Basic cache population (no id clash avoidance yet though), along with basic tracking and simpler unit tests git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@21312 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../ActionServiceImplTransactionalTest.java | 4 +- .../action/ActionTrackingServiceImpl.java | 63 ++- .../action/ActionTrackingServiceImplTest.java | 389 ++++++++++++++++++ 3 files changed, 444 insertions(+), 12 deletions(-) create mode 100644 source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java 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