From f4d3f870f5749b3c68a38b7f1285219fc19b919f Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Tue, 17 Aug 2010 16:53:57 +0000 Subject: [PATCH] Scheduled action executor - Unit tests for most execution cases, persistance still TODO (ALF-4346) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@21846 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../repo/action/ActionServiceImplTest.java | 13 +- .../action/ActionTrackingServiceImplTest.java | 1 + .../ScheduledPersistedActionImpl.java | 8 + .../ScheduledPersistedActionServiceImpl.java | 28 ++- .../ScheduledPersistedActionServiceTest.java | 196 ++++++++++++++++-- .../scheduled/ScheduledPersistedAction.java | 3 +- 6 files changed, 226 insertions(+), 23 deletions(-) diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java index 10b60078d1..7dd8eec2d8 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java @@ -1265,11 +1265,13 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest public static final String NAME = "sleep-action"; public static final String GO_BANG = "GoBang"; private int sleepMs; + + private Thread executingThread; private int timesExecuted = 0; private void incrementTimesExecutedCount() {timesExecuted++;} public int getTimesExecuted() {return timesExecuted;} - private Thread executingThread; + public void resetTimesExecuted() {timesExecuted=0;} private ActionTrackingService actionTrackingService; @@ -1341,11 +1343,12 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest throw new RuntimeException("Bang!"); } - CancellableSleepAction ca = (CancellableSleepAction)action; - boolean cancelled = actionTrackingService.isCancellationRequested(ca); - if(cancelled) + if(action instanceof CancellableSleepAction) { - throw new ActionCancelledException(ca); + CancellableSleepAction ca = (CancellableSleepAction)action; + boolean cancelled = actionTrackingService.isCancellationRequested(ca); + if(cancelled) + throw new ActionCancelledException(ca); } } } diff --git a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java index ec2dc01a75..93ec6c89dc 100644 --- a/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionTrackingServiceImplTest.java @@ -301,6 +301,7 @@ public class ActionTrackingServiceImplTest extends TestCase { final SleepActionExecuter sleepActionExec = (SleepActionExecuter)ctx.getBean(SleepActionExecuter.NAME); + sleepActionExec.resetTimesExecuted(); sleepActionExec.setSleepMs(10000); // Have it run asynchronously diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java index 4c0e37e0f4..53d9873278 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java @@ -58,6 +58,14 @@ public class ScheduledPersistedActionImpl implements ScheduledPersistedAction return persistedAtNodeRef; } + /** + * Record where this schedule is persisted + */ + protected void setPersistedAtNodeRef(NodeRef nodeRef) + { + this.persistedAtNodeRef = nodeRef; + } + /** Get the action which the schedule applies to */ public Action getAction() { diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java index 8e61d9f185..7459dfb5b4 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java @@ -28,6 +28,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ActionModel; import org.alfresco.repo.action.RuntimeActionService; import org.alfresco.repo.model.Repository; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction; @@ -150,10 +151,16 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc */ public void saveSchedule(ScheduledPersistedAction schedule) { + // Remove if already there removeFromScheduler((ScheduledPersistedActionImpl) schedule); + + // TODO Create the node + relationship if not already there + + // Save to the repo + // TODO Persist details + + // Add to the scheduler again addToScheduler((ScheduledPersistedActionImpl) schedule); - - // TODO } /** @@ -162,8 +169,10 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc */ public void deleteSchedule(ScheduledPersistedAction schedule) { + // Remove from the scheduler removeFromScheduler((ScheduledPersistedActionImpl) schedule); + // Now remove from the repo // TODO } @@ -173,7 +182,8 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc */ public ScheduledPersistedAction getSchedule(Action persistedAction) { - // TODO + // TODO look for a relationship of the special type from + // the action, then if we find it load the schedule via it return null; } @@ -209,6 +219,11 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc protected void removeFromScheduler(ScheduledPersistedActionImpl schedule) { // Jobs are indexed by the persisted node ref + // So, only try to remove if persisted + if(schedule.getPersistedAtNodeRef() == null) + return; + + // Ask to remove it try { scheduler.deleteJob(schedule.getPersistedAtNodeRef().toString(), SCHEDULER_GROUP); @@ -307,9 +322,14 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc public void execute(JobExecutionContext jobContext) { + // Do all this work as system + // TODO - See if we can pinch some bits from the existing scheduled + // actions around who to run as + AuthenticationUtil.setRunAsUserSystem(); + // Create the action object NodeRef actionNodeRef = new NodeRef( - (String)jobContext.get(JOB_ACTION_NODEREF) + jobContext.getMergedJobDataMap().getString(JOB_ACTION_NODEREF) ); Action action = runtimeActionService.createAction( actionNodeRef diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java index 458d639dfd..9cfb6efa36 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java @@ -24,11 +24,18 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionImpl; +import org.alfresco.repo.action.RuntimeActionService; +import org.alfresco.repo.action.ActionServiceImplTest.SleepActionExecuter; +import org.alfresco.repo.action.executer.ActionExecuter; import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction; import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedActionService; +import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction.IntervalPeriod; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -56,11 +63,16 @@ public class ScheduledPersistedActionServiceTest extends TestCase private static ConfigurableApplicationContext ctx = (ConfigurableApplicationContext)ApplicationContextHelper.getApplicationContext(); + private ScheduledPersistedActionService service; + private Scheduler scheduler; + private TransactionService transactionService; + private RuntimeActionService runtimeActionService; private ActionService actionService; private NodeService nodeService; private Repository repositoryHelper; + private Action testAction; private NodeRef scheduledRoot; @Override @@ -70,6 +82,9 @@ public class ScheduledPersistedActionServiceTest extends TestCase nodeService = (NodeService) ctx.getBean("nodeService"); repositoryHelper = (Repository) ctx.getBean("repositoryHelper"); transactionService = (TransactionService) ctx.getBean("transactionService"); + runtimeActionService = (RuntimeActionService) ctx.getBean("actionService"); + service = (ScheduledPersistedActionService) ctx.getBean("scheduledPersistedActionService"); + scheduler = (Scheduler) ctx.getBean("schedulerFactory"); // Set the current security context as admin @@ -89,6 +104,18 @@ public class ScheduledPersistedActionServiceTest extends TestCase } } + // Register the test executor, if needed + SleepActionExecuter.registerIfNeeded(ctx); + + // Persist an action that uses the test executor + testAction = new TestAction(actionService.createAction(SleepActionExecuter.NAME)); + NodeRef actionNodeRef = runtimeActionService.createActionNodeRef(// + testAction, + ScheduledPersistedActionServiceImpl.SCHEDULED_ACTION_ROOT_NODE_REF, + ContentModel.ASSOC_CONTAINS, + QName.createQName("TestAction") + ); + // Finish setup txn.commit(); } @@ -161,27 +188,161 @@ public class ScheduledPersistedActionServiceTest extends TestCase */ public void testExecution() throws Exception { - // A job due to start in 2 seconds - // TODO + final SleepActionExecuter sleepActionExec = + (SleepActionExecuter)ctx.getBean(SleepActionExecuter.NAME); + sleepActionExec.resetTimesExecuted(); + sleepActionExec.setSleepMs(1); + + ScheduledPersistedAction schedule; + + + // Until the schedule is persisted, nothing will happen + schedule = service.createSchedule(testAction); + assertEquals(0, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); + + + // A job due to start in 1 second, and run once + schedule = service.createSchedule(testAction); + schedule.setScheduleStart( + new Date(System.currentTimeMillis()+1000) + ); + assertNull(schedule.getScheduleInterval()); + assertNull(schedule.getScheduleIntervalCount()); + assertNull(schedule.getScheduleIntervalPeriod()); + + // TODO - Remove this hacky workaround when real persistence is in + ((ScheduledPersistedActionImpl)schedule).setPersistedAtNodeRef( + testAction.getNodeRef() + ); + System.out.println("Job starts in 1 second, no repeat..."); + service.saveSchedule(schedule); + + // Check it went in + assertEquals(1, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); + + // Let it run + Thread.sleep(2000); + + // Ensure it did properly run the once + assertEquals(1, sleepActionExec.getTimesExecuted()); + + // Should have removed itself now the schedule is over + assertEquals(0, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); + + // Zap it + service.deleteSchedule(schedule); + assertEquals(0, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); + + + // ========================== + + + // A job that runs every 2 seconds, for the next 3.5 seconds + // (Should get to run twice, now and @2 secs) + schedule = service.createSchedule(testAction); + schedule.setScheduleStart( + new Date(0) + ); + ((ScheduledPersistedActionImpl)schedule).setScheduleEnd( + new Date(System.currentTimeMillis()+3500) + ); + schedule.setScheduleIntervalCount(2); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Second); + assertEquals("2s", schedule.getScheduleInterval()); + + // Reset count + sleepActionExec.resetTimesExecuted(); + assertEquals(0, sleepActionExec.getTimesExecuted()); + + // TODO - Remove this hacky workaround when real persistence is in + ((ScheduledPersistedActionImpl)schedule).setPersistedAtNodeRef( + testAction.getNodeRef() + ); + System.out.println("Job starts now, repeats twice @ 2s"); + service.saveSchedule(schedule); + + Thread.sleep(4000); + + // Ensure it did properly run twice times + assertEquals(2, sleepActionExec.getTimesExecuted()); + + // Zap it + service.deleteSchedule(schedule); + assertEquals(0, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); + + + // ========================== - // A job that runs every 2 seconds - // TODO // A job that starts in 2 seconds time, and runs - // every second + // every second until we kill it + schedule = service.createSchedule(testAction); + schedule.setScheduleStart( + new Date(System.currentTimeMillis()+2000) + ); + schedule.setScheduleIntervalCount(1); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Second); + assertEquals("1s", schedule.getScheduleInterval()); + + // Reset count + sleepActionExec.resetTimesExecuted(); + assertEquals(0, sleepActionExec.getTimesExecuted()); + + // TODO - Remove this hacky workaround when real persistence is in + ((ScheduledPersistedActionImpl)schedule).setPersistedAtNodeRef( + testAction.getNodeRef() + ); + System.out.println("Job starts in 2s, repeats @ 1s"); + service.saveSchedule(schedule); + + // Let it run a few times + Thread.sleep(5000); + + // Zap it - should still be live + assertEquals(1, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); + service.deleteSchedule(schedule); + assertEquals(0, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); + + // Check it ran an appropriate number of times + assertEquals( + "Didn't run enough - " + sleepActionExec.getTimesExecuted(), + true, + sleepActionExec.getTimesExecuted() >= 3 + ); + assertEquals( + "Ran too much - " + sleepActionExec.getTimesExecuted(), + true, + sleepActionExec.getTimesExecuted() < 5 + ); + + // Ensure it finished shutting down + Thread.sleep(500); + } + + /** + * Tests that when we have more than one schedule + * defined and active, then the correct things run + * at the correct times, and we never get confused + */ + public void DISABLEDtestMultipleExecutions() throws Exception + { + // Create one that starts running in 2 seconds, runs every 2 seconds + // until 9 seconds are up (will run 4 times) + + // Create one that starts running now, every second until 9.5 seconds + // are up (will run 9-10 times) + + // Set them going + + // Wait + + // Check that they really did run properly // TODO } // ============================================================================ - /** - * An action that updates a static count, so we - * can tell how often it is run. - * We have one of these persisted in the repository during - * the tests - * TODO - */ - + /** * For unit testing only - not thread safe! */ @@ -204,4 +365,13 @@ public class ScheduledPersistedActionServiceTest extends TestCase ran = true; } } + + protected static class TestAction extends ActionImpl + { + protected TestAction(Action action) + { + super(action); + } + } + } diff --git a/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java b/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java index 852cea6a18..e03573ecb8 100644 --- a/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java +++ b/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java @@ -94,7 +94,8 @@ public interface ScheduledPersistedAction Week ('W'), Day ('D'), Hour ('h'), - Minute ('m'); + Minute ('m'), + Second ('s'); private final char letter; IntervalPeriod(char letter) {