mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
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:
@@ -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;
|
||||
|
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user