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
This commit is contained in:
Nick Burch
2010-07-20 17:00:29 +00:00
parent 5d2143cfe5
commit 6f828a8b97
3 changed files with 444 additions and 12 deletions

View File

@@ -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;

View File

@@ -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
);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String, ExecutionDetails> 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<String, ExecutionDetails>)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<ParameterDefinition> 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();
}
}
}
}