From 1f5c3a1d202972952dceb9616379d1d01a68fab2 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Wed, 1 Sep 2010 16:16:32 +0000 Subject: [PATCH] Improve how scheduled actions with historic start dates are handled during startup. The last run time is used to decide when they need to be re-scheduled for (ALF-4505) Also, don't add the new scheduled action to Quartz until the transaction commits, to ensure the nodes are really there before Quartz fires git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@22136 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../ScheduledPersistedActionImpl.java | 98 +- .../ScheduledPersistedActionServiceImpl.java | 40 +- .../ScheduledPersistedActionServiceTest.java | 2001 ++++++++++------- 3 files changed, 1298 insertions(+), 841 deletions(-) diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java index 26d1abd22b..ad3a94795d 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java @@ -205,36 +205,116 @@ public class ScheduledPersistedActionImpl implements ScheduledPersistedAction return null; } + // If the end date is in the past, don't schedule + if(scheduleEnd != null && scheduleEnd.getTime() < System.currentTimeMillis()) + { + return null; + } + + + // Decide what to use as the start time + // This will be based on the requested start time, when + // the schedule last ran (if ever), and the interval + Date startAt = scheduleStart; + Date endAt = scheduleEnd; + if(startAt == null) + { + // No start time specified, so start immediately + startAt = new Date(); + } + else + { + // A start time was given. Is it still to come? + if(startAt.getTime() >= System.currentTimeMillis()) + { + // The start date hasn't happened yet + // This means we can just use it as-is + } + else + { + // The start date has passed. Have we ever run this? + if(lastExecutedAt == null) + { + // The schedule has never run + // Tell Quartz the requested start time, so it runs + // immediately, and repeats are correctly calculated + startAt = scheduleStart; + } + else if(lastExecutedAt.getTime() < startAt.getTime()) + { + // The previous run time is before the scheduled + // start time, so has probably been edited, and + // both of these dates is in the past. + // Tell Quartz the requested start time, so it runs + // immediately, and repeats are correctly calculated + startAt = scheduleStart; + } + else + { + // The start date is in the past, and we have + // previously run the job + + if(getScheduleInterval() == null) + { + // There is no schedule setup, so this is + // probably just an old job on startup/edit + // So, don't run it + return null; + } + + // Based on the start time, when would it next be + // due to fire? + DateIntervalTrigger testT = buildDateIntervalTrigger("TEST", scheduleStart, null); + Date nextFireFromNow = testT.getFireTimeAfter(new Date()); + Date nextFireFromLast = testT.getFireTimeAfter(lastExecutedAt); + + // If the next fire time from the last is before the + // next due date, then we missed one + if(nextFireFromLast.getTime() < nextFireFromNow.getTime()) + { + // We missed one! + // Tell Quartz the requested start time, so it runs + // immediately, and repeats are correctly calculated + startAt = scheduleStart; + } + else + { + // The last run time was largely when due + // So, don't run until the next time + startAt = nextFireFromNow; + } + } + } + } + // If they don't want it to repeat, just use a simple interval if(getScheduleInterval() == null) { SimpleTrigger trigger = new SimpleTrigger( triggerName, null, - scheduleStart + startAt ); trigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); return trigger; } - + return buildDateIntervalTrigger(triggerName, startAt, endAt); + } + + private DateIntervalTrigger buildDateIntervalTrigger(String triggerName, Date startAt, Date endAt) + { // There are some repeating rules // Create a Date Interval trigger DateIntervalTrigger.IntervalUnit quartzInterval = DateIntervalTrigger.IntervalUnit.valueOf( intervalPeriod.toString().toUpperCase() ); - // Ensure we always have a start date, even if it's now - Date start = scheduleStart; - if(start == null) - start = new Date(); - // The end date is allowed to be null - Date end = scheduleEnd; // Create the interval DateIntervalTrigger trigger = new DateIntervalTrigger( triggerName, null, - start, end, + startAt, endAt, quartzInterval, intervalCount ); trigger.setMisfireInstruction( DateIntervalTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW ); diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java index 9e3030bd5f..916d3d3ada 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java @@ -33,7 +33,9 @@ import org.alfresco.repo.action.RuntimeActionService; import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; @@ -391,24 +393,36 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc } /** - * Adds a new entry into the scheduler + * Builds up the Quartz details, and adds it to the Quartz + * scheduler when the transaction completes. + * We have to wait for the transaction to finish, otherwise + * Quartz may end up trying and failing to load the details + * of a job that hasn't been committed to the repo yet! */ protected void addToScheduler(ScheduledPersistedActionImpl schedule) { // Wrap it up in Quartz bits - JobDetail details = buildJobDetail(schedule); - Trigger trigger = schedule.asTrigger(); + final JobDetail details = buildJobDetail(schedule); + final Trigger trigger = schedule.asTrigger(); - // Schedule it - try - { - scheduler.scheduleJob(details, trigger); - } - catch (SchedulerException e) - { - // Probably means scheduler is shutting down - log.warn(e); - } + // As soon as the transaction commits, add it + AlfrescoTransactionSupport.bindListener( + new TransactionListenerAdapter() { + @Override + public void afterCommit() { + // Schedule it with Quartz + try + { + scheduler.scheduleJob(details, trigger); + } + catch (SchedulerException e) + { + // Probably means scheduler is shutting down + log.warn(e); + } + } + } + ); } protected JobDetail buildJobDetail(ScheduledPersistedActionImpl schedule) diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java index 050ec92a97..352ff61f75 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java @@ -29,6 +29,7 @@ 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.scheduled.ScheduledPersistedActionServiceImpl.ScheduledPersistedActionServiceBootstrap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; @@ -59,11 +60,12 @@ import org.springframework.context.ConfigurableApplicationContext; /** * Unit tests for the {@link ScheduledPersistedActionService} */ -public class ScheduledPersistedActionServiceTest extends TestCase +public class ScheduledPersistedActionServiceTest extends TestCase { - private static ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) ApplicationContextHelper - .getApplicationContext(); + private static ConfigurableApplicationContext ctx = + (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext(); + private ScheduledPersistedActionServiceBootstrap bootstrap; private ScheduledPersistedActionService service; private ScheduledPersistedActionServiceImpl serviceImpl; private Scheduler scheduler; @@ -78,893 +80,1254 @@ public class ScheduledPersistedActionServiceTest extends TestCase private Action testAction3; @Override - protected void setUp() throws Exception + protected void setUp() throws Exception { - actionService = (ActionService) ctx.getBean("actionService"); - nodeService = (NodeService) ctx.getBean("nodeService"); - transactionService = (TransactionService) ctx.getBean("transactionService"); - runtimeActionService = (RuntimeActionService) ctx.getBean("actionService"); - service = (ScheduledPersistedActionService) ctx.getBean("ScheduledPersistedActionService"); - serviceImpl = (ScheduledPersistedActionServiceImpl) ctx.getBean("scheduledPersistedActionService"); - scheduler = (Scheduler) ctx.getBean("schedulerFactory"); + actionService = (ActionService) ctx.getBean("actionService"); + nodeService = (NodeService) ctx.getBean("nodeService"); + transactionService = (TransactionService) ctx + .getBean("transactionService"); + runtimeActionService = (RuntimeActionService) ctx + .getBean("actionService"); + service = (ScheduledPersistedActionService) ctx + .getBean("ScheduledPersistedActionService"); + serviceImpl = (ScheduledPersistedActionServiceImpl) ctx + .getBean("scheduledPersistedActionService"); + scheduler = (Scheduler) ctx.getBean("schedulerFactory"); + bootstrap = (ScheduledPersistedActionServiceBootstrap) ctx + .getBean("scheduledPersistedActionServiceBootstrap"); - // Set the current security context as admin - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + // Set the current security context as admin + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil + .getAdminUserName()); - UserTransaction txn = transactionService.getUserTransaction(); - txn.begin(); + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); - // Register the test executor, if needed - SleepActionExecuter.registerIfNeeded(ctx); + // Register the test executor, if needed + SleepActionExecuter.registerIfNeeded(ctx); - // Zap all test schedules - List schedules = service.listSchedules(); - for (ScheduledPersistedAction schedule : schedules) - { - service.deleteSchedule(schedule); - } + // Zap all test schedules + List schedules = service.listSchedules(); + for (ScheduledPersistedAction schedule : schedules) + { + service.deleteSchedule(schedule); + } - // Persist an action that uses the test executor - testAction = new TestAction(actionService.createAction(SleepActionExecuter.NAME)); - runtimeActionService.createActionNodeRef( - // - testAction, ScheduledPersistedActionServiceImpl.SCHEDULED_ACTION_ROOT_NODE_REF, - ContentModel.ASSOC_CONTAINS, QName.createQName("TestAction")); + // Persist an action that uses the test executor + testAction = new TestAction(actionService + .createAction(SleepActionExecuter.NAME)); + runtimeActionService.createActionNodeRef( + // + testAction, + ScheduledPersistedActionServiceImpl.SCHEDULED_ACTION_ROOT_NODE_REF, + ContentModel.ASSOC_CONTAINS, QName.createQName("TestAction")); - testAction2 = new TestAction(actionService.createAction(SleepActionExecuter.NAME)); - runtimeActionService.createActionNodeRef( - // - testAction2, ScheduledPersistedActionServiceImpl.SCHEDULED_ACTION_ROOT_NODE_REF, - ContentModel.ASSOC_CONTAINS, QName.createQName("TestAction2")); - - testAction3 = new TestAction(actionService.createAction(SleepActionExecuter.NAME)); + testAction2 = new TestAction(actionService + .createAction(SleepActionExecuter.NAME)); + runtimeActionService.createActionNodeRef( + // + testAction2, + ScheduledPersistedActionServiceImpl.SCHEDULED_ACTION_ROOT_NODE_REF, + ContentModel.ASSOC_CONTAINS, QName.createQName("TestAction2")); - // Finish setup - txn.commit(); - - // By default, we don't want the scheduler to fire while the tests run - // Certain tests will enable it as required - scheduler.standby(); + testAction3 = new TestAction(actionService + .createAction(SleepActionExecuter.NAME)); + + // Finish setup + txn.commit(); + + // By default, we don't want the scheduler to fire while the tests run + // Certain tests will enable it as required + scheduler.standby(); } @Override - protected void tearDown() throws Exception + protected void tearDown() throws Exception { - UserTransaction txn = transactionService.getUserTransaction(); - txn.begin(); - - // Zap all test schedules - List schedules = service.listSchedules(); - for (ScheduledPersistedAction schedule : schedules) - { - service.deleteSchedule(schedule); - } - txn.commit(); - - // Re-enable the scheduler again - scheduler.start(); - } + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); - /** - * Test that the {@link ScheduledPersistedAction} implementation behaves - * properly - */ - public void testPersistedActionImpl() throws Exception - { - ScheduledPersistedActionImpl schedule = - new ScheduledPersistedActionImpl(testAction); - ScheduledPersistedActionImpl schedule3 = - new ScheduledPersistedActionImpl(testAction3); + // Zap all test schedules + List schedules = service.listSchedules(); + for (ScheduledPersistedAction schedule : schedules) + { + service.deleteSchedule(schedule); + } + txn.commit(); - - // Check the core bits - assertEquals(null, schedule.getPersistedAtNodeRef()); - assertEquals(testAction, schedule.getAction()); - assertEquals(testAction.getNodeRef(), schedule.getActionNodeRef()); - - assertEquals(null, schedule3.getPersistedAtNodeRef()); - assertEquals(testAction3, schedule3.getAction()); - assertEquals(null, schedule3.getActionNodeRef()); - - // Persist the 3rd action - runtimeActionService.createActionNodeRef( - // - testAction3, ScheduledPersistedActionServiceImpl.SCHEDULED_ACTION_ROOT_NODE_REF, - ContentModel.ASSOC_CONTAINS, QName.createQName("TestAction3")); - - assertEquals(null, schedule.getPersistedAtNodeRef()); - assertEquals(testAction, schedule.getAction()); - assertEquals(testAction.getNodeRef(), schedule.getActionNodeRef()); - - assertEquals(null, schedule3.getPersistedAtNodeRef()); - assertEquals(testAction3, schedule3.getAction()); - assertEquals(testAction3.getNodeRef(), schedule3.getActionNodeRef()); - assertNotNull(schedule3.getAction().getNodeRef()); - - - // Check the start/end date bits - assertEquals(null, schedule.getScheduleStart()); - assertEquals(null, schedule.getScheduleEnd()); - - schedule.setScheduleStart(new Date(1234)); - assertEquals(1234, schedule.getScheduleStart().getTime()); - assertEquals(null, schedule.getScheduleEnd()); - - schedule.setScheduleEnd(new Date(4321)); - assertEquals(1234, schedule.getScheduleStart().getTime()); - assertEquals(4321, schedule.getScheduleEnd().getTime()); - - assertEquals(null, schedule3.getScheduleStart()); - assertEquals(null, schedule3.getScheduleEnd()); - - schedule.setScheduleStart(null); - assertEquals(null, schedule.getScheduleStart()); - assertEquals(4321, schedule.getScheduleEnd().getTime()); - - schedule.setScheduleEnd(null); - assertEquals(null, schedule.getScheduleStart()); - assertEquals(null, schedule.getScheduleEnd()); - - - // Check the interval parts - assertEquals(null, schedule.getScheduleInterval()); - assertEquals(null, schedule.getScheduleIntervalCount()); - assertEquals(null, schedule.getScheduleIntervalPeriod()); - - schedule.setScheduleIntervalCount(3); - assertEquals(null, schedule.getScheduleInterval()); - assertEquals(3, schedule.getScheduleIntervalCount().intValue()); - assertEquals(null, schedule.getScheduleIntervalPeriod()); - - schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); - assertEquals("3Hour", schedule.getScheduleInterval()); - assertEquals(3, schedule.getScheduleIntervalCount().intValue()); - assertEquals(IntervalPeriod.Hour, schedule.getScheduleIntervalPeriod()); - - schedule.setScheduleIntervalCount(8); - schedule.setScheduleIntervalPeriod(IntervalPeriod.Month); - assertEquals("8Month", schedule.getScheduleInterval()); - assertEquals(8, schedule.getScheduleIntervalCount().intValue()); - assertEquals(IntervalPeriod.Month, schedule.getScheduleIntervalPeriod()); - - schedule.setScheduleIntervalCount(null); - assertEquals(null, schedule.getScheduleInterval()); - assertEquals(null, schedule.getScheduleIntervalCount()); - assertEquals(IntervalPeriod.Month, schedule.getScheduleIntervalPeriod()); - - schedule.setScheduleIntervalPeriod(null); - assertEquals(null, schedule.getScheduleInterval()); - assertEquals(null, schedule.getScheduleIntervalCount()); - assertEquals(null, schedule.getScheduleIntervalPeriod()); - - // Trigger parts happen in another test - } + // Re-enable the scheduler again + scheduler.start(); + } - /** - * Tests that the to-trigger stuff works properly - */ - public void testActionToTrigger() throws Exception - { - // Can't get a trigger until persisted - ScheduledPersistedActionImpl schedule = - (ScheduledPersistedActionImpl)service.createSchedule(testAction); - Trigger t; - try { - schedule.asTrigger(); - fail("Should require persistence first"); - } catch(IllegalStateException e) {} + /** + * Test that the {@link ScheduledPersistedAction} implementation behaves + * properly + */ + public void testPersistedActionImpl() throws Exception + { + ScheduledPersistedActionImpl schedule = + new ScheduledPersistedActionImpl(testAction); + ScheduledPersistedActionImpl schedule3 = + new ScheduledPersistedActionImpl(testAction3); - service.saveSchedule(schedule); - schedule.asTrigger(); - - - // No schedule, no trigger - assertEquals(null, schedule.getScheduleInterval()); - assertEquals(null, schedule.getScheduleIntervalCount()); - assertEquals(null, schedule.getScheduleIntervalPeriod()); - assertEquals(null, schedule.asTrigger()); - - - // Only start date - schedule.setScheduleStart(new Date(12345)); - - t = schedule.asTrigger(); - assertNotNull(t); - assertEquals(12345, t.getStartTime().getTime()); - assertEquals(null, t.getEndTime()); - assertEquals(SimpleTrigger.class, t.getClass()); - - - // Only end date - // (End date + no repeat = never schedule) - schedule.setScheduleStart(null); - schedule.setScheduleEnd(new Date(12345)); - - t = schedule.asTrigger(); - assertEquals(null, t); + // Check the core bits + assertEquals(null, schedule.getPersistedAtNodeRef()); + assertEquals(testAction, schedule.getAction()); + assertEquals(testAction.getNodeRef(), schedule.getActionNodeRef()); - - // Only interval - schedule.setScheduleStart(null); - schedule.setScheduleEnd(null); - schedule.setScheduleIntervalCount(2); - schedule.setScheduleIntervalPeriod(IntervalPeriod.Second); - - t = schedule.asTrigger(); - assertNotNull(t); - assertEquals((double)System.currentTimeMillis(), (double)t.getStartTime().getTime(), 5); // Within 5ms - assertEquals(null, t.getEndTime()); - assertEquals(DateIntervalTrigger.class, t.getClass()); - assertEquals(2, ((DateIntervalTrigger)t).getRepeatInterval()); - assertEquals(IntervalUnit.SECOND, ((DateIntervalTrigger)t).getRepeatIntervalUnit()); + assertEquals(null, schedule3.getPersistedAtNodeRef()); + assertEquals(testAction3, schedule3.getAction()); + assertEquals(null, schedule3.getActionNodeRef()); - - // Start+interval - schedule.setScheduleStart(new Date(12345)); - schedule.setScheduleEnd(null); - schedule.setScheduleIntervalCount(3); - schedule.setScheduleIntervalPeriod(IntervalPeriod.Month); - - t = schedule.asTrigger(); - assertNotNull(t); - assertEquals(12345, t.getStartTime().getTime()); - assertEquals(null, t.getEndTime()); - assertEquals(DateIntervalTrigger.class, t.getClass()); - assertEquals(3, ((DateIntervalTrigger)t).getRepeatInterval()); - assertEquals(IntervalUnit.MONTH, ((DateIntervalTrigger)t).getRepeatIntervalUnit()); + // Persist the 3rd action + runtimeActionService.createActionNodeRef( + // + testAction3, + ScheduledPersistedActionServiceImpl.SCHEDULED_ACTION_ROOT_NODE_REF, + ContentModel.ASSOC_CONTAINS, QName.createQName("TestAction3")); - - // Start+interval+end - schedule.setScheduleStart(new Date(12345)); - schedule.setScheduleEnd(new Date(54321)); - schedule.setScheduleIntervalCount(12); - schedule.setScheduleIntervalPeriod(IntervalPeriod.Week); - - t = schedule.asTrigger(); - assertNotNull(t); - assertEquals(12345, t.getStartTime().getTime()); - assertEquals(54321, t.getEndTime().getTime()); - assertEquals(DateIntervalTrigger.class, t.getClass()); - assertEquals(12, ((DateIntervalTrigger)t).getRepeatInterval()); - assertEquals(IntervalUnit.WEEK, ((DateIntervalTrigger)t).getRepeatIntervalUnit()); + assertEquals(null, schedule.getPersistedAtNodeRef()); + assertEquals(testAction, schedule.getAction()); + assertEquals(testAction.getNodeRef(), schedule.getActionNodeRef()); - - // interval+end - long future = System.currentTimeMillis() + 1234567; - schedule.setScheduleStart(null); - schedule.setScheduleEnd(new Date(future)); - schedule.setScheduleIntervalCount(6); - schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); - - t = schedule.asTrigger(); - assertNotNull(t); - assertEquals((double)System.currentTimeMillis(), (double)t.getStartTime().getTime(), 2); // Within 2ms - assertEquals(future, t.getEndTime().getTime()); - assertEquals(DateIntervalTrigger.class, t.getClass()); - assertEquals(6, ((DateIntervalTrigger)t).getRepeatInterval()); - assertEquals(IntervalUnit.HOUR, ((DateIntervalTrigger)t).getRepeatIntervalUnit()); - - - // Start+end - // (The end bit is ignored, as there's no interval to decide over) - schedule.setScheduleStart(new Date(12345)); - schedule.setScheduleEnd(new Date(54321)); - schedule.setScheduleIntervalCount(null); - schedule.setScheduleIntervalPeriod(null); - - t = schedule.asTrigger(); - assertNotNull(t); - assertEquals(12345, t.getStartTime().getTime()); - assertEquals(null, t.getEndTime()); - assertEquals(SimpleTrigger.class, t.getClass()); - } + assertEquals(null, schedule3.getPersistedAtNodeRef()); + assertEquals(testAction3, schedule3.getAction()); + assertEquals(testAction3.getNodeRef(), schedule3.getActionNodeRef()); + assertNotNull(schedule3.getAction().getNodeRef()); - /** - * Tests that we can create, save, edit, delete etc the scheduled persisted - * actions - */ - public void testCreation() - { - ScheduledPersistedAction schedule = service.createSchedule(testAction); - assertNotNull(schedule); - assertTrue(testAction == schedule.getAction()); - assertEquals(testAction.getNodeRef(), schedule.getAction().getNodeRef()); + // Check the start/end date bits + assertEquals(null, schedule.getScheduleStart()); + assertEquals(null, schedule.getScheduleEnd()); - assertNull(schedule.getScheduleStart()); - assertNull(schedule.getScheduleInterval()); - assertNull(schedule.getScheduleIntervalCount()); - assertNull(schedule.getScheduleIntervalPeriod()); - - Date now = new Date(); - schedule.setScheduleStart(now); - assertEquals(now, schedule.getScheduleStart()); - schedule.setScheduleIntervalCount(2); - assertEquals(new Integer(2), schedule.getScheduleIntervalCount()); - schedule.setScheduleIntervalPeriod(ScheduledPersistedAction.IntervalPeriod.Day); - assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, schedule.getScheduleIntervalPeriod()); - } + schedule.setScheduleStart(new Date(1234)); + assertEquals(1234, schedule.getScheduleStart().getTime()); + assertEquals(null, schedule.getScheduleEnd()); - public void testCreateSaveLoad() throws Exception - { - // create and save schedule - ScheduledPersistedAction schedule = service.createSchedule(testAction); - assertNotNull(schedule); - Date now = new Date(); - schedule.setScheduleStart(now); - schedule.setScheduleIntervalCount(2); - schedule.setScheduleIntervalPeriod(ScheduledPersistedAction.IntervalPeriod.Day); + schedule.setScheduleEnd(new Date(4321)); + assertEquals(1234, schedule.getScheduleStart().getTime()); + assertEquals(4321, schedule.getScheduleEnd().getTime()); - assertNull( ((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef() ); - service.saveSchedule(schedule); - assertNotNull( ((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef() ); + assertEquals(null, schedule3.getScheduleStart()); + assertEquals(null, schedule3.getScheduleEnd()); - // Load it again, should have the same details still - ScheduledPersistedAction retrieved = serviceImpl.loadPersistentSchedule(((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef()); - assertNotNull(retrieved); - assertEquals(testAction.getNodeRef(), retrieved.getAction().getNodeRef()); - assertEquals(now, retrieved.getScheduleStart()); - assertEquals(new Integer(2), retrieved.getScheduleIntervalCount()); - assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, retrieved.getScheduleIntervalPeriod()); - assertNotNull( ((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef() ); + schedule.setScheduleStart(null); + assertEquals(null, schedule.getScheduleStart()); + assertEquals(4321, schedule.getScheduleEnd().getTime()); - // Load a 2nd copy, won't be any changes - ScheduledPersistedAction second = serviceImpl.loadPersistentSchedule(((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef()); - assertNotNull(second); - assertEquals(testAction.getNodeRef(), second.getAction().getNodeRef()); - assertEquals(now, second.getScheduleStart()); - assertEquals(new Integer(2), second.getScheduleIntervalCount()); - assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, second.getScheduleIntervalPeriod()); - - - // Now ensure we can create for an action that didn't have a noderef - // when we started - schedule = service.createSchedule(testAction3); - - assertNull(schedule.getActionNodeRef()); - assertNull( ((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef() ); - - runtimeActionService.createActionNodeRef( - // - testAction3, ScheduledPersistedActionServiceImpl.SCHEDULED_ACTION_ROOT_NODE_REF, - ContentModel.ASSOC_CONTAINS, QName.createQName("TestAction3")); - - assertNotNull(schedule.getActionNodeRef()); - assertNull( ((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef() ); - - service.saveSchedule(schedule); - - assertNotNull(schedule.getActionNodeRef()); - assertNotNull( ((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef() ); - } - - /** - * Ensures that we can create, save, edit, save - * load, edit, save, load etc, all without - * problems, and without creating duplicates - */ - public void testEditing() throws Exception - { - // create and save schedule - ScheduledPersistedAction schedule = service.createSchedule(testAction); - assertNotNull(schedule); - Date now = new Date(); - schedule.setScheduleStart(now); - schedule.setScheduleIntervalCount(2); - schedule.setScheduleIntervalPeriod(ScheduledPersistedAction.IntervalPeriod.Day); - - UserTransaction txn = transactionService.getUserTransaction(); - txn.begin(); - service.saveSchedule(schedule); - txn.commit(); + schedule.setScheduleEnd(null); + assertEquals(null, schedule.getScheduleStart()); + assertEquals(null, schedule.getScheduleEnd()); - // Load and check it hasn't changed - txn = transactionService.getUserTransaction(); - txn.begin(); - ScheduledPersistedAction retrieved = serviceImpl.loadPersistentSchedule(((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef()); - txn.commit(); - - assertNotNull(retrieved); - assertEquals(testAction.getNodeRef(), retrieved.getAction().getNodeRef()); - assertEquals(now, retrieved.getScheduleStart()); - assertEquals(new Integer(2), retrieved.getScheduleIntervalCount()); - assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, retrieved.getScheduleIntervalPeriod()); + // Check the interval parts + assertEquals(null, schedule.getScheduleInterval()); + assertEquals(null, schedule.getScheduleIntervalCount()); + assertEquals(null, schedule.getScheduleIntervalPeriod()); - // Save and re-load without changes - txn = transactionService.getUserTransaction(); - txn.begin(); - service.saveSchedule(schedule); - retrieved = serviceImpl.loadPersistentSchedule(((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef()); - txn.commit(); - - assertNotNull(retrieved); - assertEquals(testAction.getNodeRef(), retrieved.getAction().getNodeRef()); - assertEquals(now, retrieved.getScheduleStart()); - assertEquals(new Integer(2), retrieved.getScheduleIntervalCount()); - assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, retrieved.getScheduleIntervalPeriod()); + schedule.setScheduleIntervalCount(3); + assertEquals(null, schedule.getScheduleInterval()); + assertEquals(3, schedule.getScheduleIntervalCount().intValue()); + assertEquals(null, schedule.getScheduleIntervalPeriod()); - // Make some small changes - txn = transactionService.getUserTransaction(); - txn.begin(); - retrieved = serviceImpl.loadPersistentSchedule(((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef()); - retrieved.setScheduleIntervalCount(3); - service.saveSchedule(retrieved); - retrieved = serviceImpl.loadPersistentSchedule(((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef()); - txn.commit(); - - assertNotNull(retrieved); - assertEquals(testAction.getNodeRef(), retrieved.getAction().getNodeRef()); - assertEquals(now, retrieved.getScheduleStart()); - assertEquals(new Integer(3), retrieved.getScheduleIntervalCount()); - assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, retrieved.getScheduleIntervalPeriod()); - - // And some more changes - retrieved.setScheduleIntervalPeriod(ScheduledPersistedAction.IntervalPeriod.Month); - now = new Date(); - retrieved.setScheduleStart(now); - - txn = transactionService.getUserTransaction(); - txn.begin(); - service.saveSchedule(retrieved); - retrieved = serviceImpl.loadPersistentSchedule(((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef()); - txn.commit(); - - assertNotNull(retrieved); - assertEquals(testAction.getNodeRef(), retrieved.getAction().getNodeRef()); - assertEquals(now, retrieved.getScheduleStart()); - assertEquals(new Integer(3), retrieved.getScheduleIntervalCount()); - assertEquals(ScheduledPersistedAction.IntervalPeriod.Month, retrieved.getScheduleIntervalPeriod()); - } - - /** - * Tests that the listings work, both of all scheduled, and from an action - */ - public void testLoadList() throws Exception - { - assertEquals(0, service.listSchedules().size()); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); + assertEquals("3Hour", schedule.getScheduleInterval()); + assertEquals(3, schedule.getScheduleIntervalCount().intValue()); + assertEquals(IntervalPeriod.Hour, schedule.getScheduleIntervalPeriod()); - // Create - ScheduledPersistedAction schedule1 = service.createSchedule(testAction); - assertNotNull(schedule1); - ScheduledPersistedAction schedule2 = service.createSchedule(testAction2); - assertNotNull(schedule2); - - assertEquals(0, service.listSchedules().size()); + schedule.setScheduleIntervalCount(8); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Month); + assertEquals("8Month", schedule.getScheduleInterval()); + assertEquals(8, schedule.getScheduleIntervalCount().intValue()); + assertEquals(IntervalPeriod.Month, schedule.getScheduleIntervalPeriod()); - service.saveSchedule(schedule1); - - assertEquals(1, service.listSchedules().size()); - assertEquals(testAction.getNodeRef(), service.listSchedules().get(0).getActionNodeRef()); - - service.saveSchedule(schedule2); - assertEquals(2, service.listSchedules().size()); - } + schedule.setScheduleIntervalCount(null); + assertEquals(null, schedule.getScheduleInterval()); + assertEquals(null, schedule.getScheduleIntervalCount()); + assertEquals(IntervalPeriod.Month, schedule.getScheduleIntervalPeriod()); - public void testLoadFromAction() throws Exception - { - // Create schedule - ScheduledPersistedAction schedule1 = service.createSchedule(testAction); - assertNotNull(schedule1); - service.saveSchedule(schedule1); + schedule.setScheduleIntervalPeriod(null); + assertEquals(null, schedule.getScheduleInterval()); + assertEquals(null, schedule.getScheduleIntervalCount()); + assertEquals(null, schedule.getScheduleIntervalPeriod()); - // retrieve schedule for action which doesn't have schedule - ScheduledPersistedAction retrieved = service.getSchedule(testAction2); - assertNull(retrieved); - - // and from one which does - retrieved = service.getSchedule(testAction); - assertNotNull(retrieved); - assertEquals(testAction.getNodeRef(), retrieved.getActionNodeRef()); - } - - /** - * Tests that the startup registering works properly - */ - public void testStartup() throws Exception - { + // Trigger parts happen in another test + } + + /** + * Tests that the to-trigger stuff works properly + */ + public void testActionToTrigger() throws Exception + { + // Can't get a trigger until persisted + ScheduledPersistedActionImpl schedule = + (ScheduledPersistedActionImpl) service.createSchedule(testAction); + Trigger t; + try { + schedule.asTrigger(); + fail("Should require persistence first"); + } catch (IllegalStateException e) { + } + + service.saveSchedule(schedule); + schedule.asTrigger(); + + // No schedule, no trigger + assertEquals(null, schedule.getScheduleInterval()); + assertEquals(null, schedule.getScheduleIntervalCount()); + assertEquals(null, schedule.getScheduleIntervalPeriod()); + assertEquals(null, schedule.asTrigger()); + + // Only start date + schedule.setScheduleStart(new Date(12345)); + + t = schedule.asTrigger(); + assertNotNull(t); + assertEquals(12345, t.getStartTime().getTime()); + assertEquals(null, t.getEndTime()); + assertEquals(SimpleTrigger.class, t.getClass()); + + // Only end date + // (End date + no repeat = never schedule) + schedule.setScheduleStart(null); + schedule.setScheduleEnd(new Date(12345)); + + t = schedule.asTrigger(); + assertEquals(null, t); + + // Only interval + schedule.setScheduleStart(null); + schedule.setScheduleEnd(null); + schedule.setScheduleIntervalCount(2); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Second); + + t = schedule.asTrigger(); + assertNotNull(t); + assertEquals((double) System.currentTimeMillis(), (double) t + .getStartTime().getTime(), 10); // Within 10ms + assertEquals(null, t.getEndTime()); + assertEquals(DateIntervalTrigger.class, t.getClass()); + assertEquals(2, ((DateIntervalTrigger) t).getRepeatInterval()); + assertEquals(IntervalUnit.SECOND, ((DateIntervalTrigger) t) + .getRepeatIntervalUnit()); + + // Start+interval + schedule.setScheduleStart(new Date(12345)); + schedule.setScheduleEnd(null); + schedule.setScheduleIntervalCount(3); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Month); + + t = schedule.asTrigger(); + assertNotNull(t); + assertEquals(12345, t.getStartTime().getTime()); + assertEquals(null, t.getEndTime()); + assertEquals(DateIntervalTrigger.class, t.getClass()); + assertEquals(3, ((DateIntervalTrigger) t).getRepeatInterval()); + assertEquals(IntervalUnit.MONTH, ((DateIntervalTrigger) t) + .getRepeatIntervalUnit()); + + // Start+interval+end-in-the-past + schedule.setScheduleStart(new Date(12345)); + schedule.setScheduleEnd(new Date(54321)); + schedule.setScheduleIntervalCount(12); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Week); + + t = schedule.asTrigger(); + assertEquals(null, t); + + // Start+interval+end-in-the-future + long future = System.currentTimeMillis() + 1234567; + schedule.setScheduleStart(new Date(12345)); + schedule.setScheduleEnd(new Date(future)); + schedule.setScheduleIntervalCount(12); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Week); + + t = schedule.asTrigger(); + assertNotNull(t); + assertEquals(12345, t.getStartTime().getTime()); + assertEquals(future, t.getEndTime().getTime()); + assertEquals(DateIntervalTrigger.class, t.getClass()); + assertEquals(12, ((DateIntervalTrigger) t).getRepeatInterval()); + assertEquals(IntervalUnit.WEEK, ((DateIntervalTrigger) t) + .getRepeatIntervalUnit()); + + // interval+end + schedule.setScheduleStart(null); + schedule.setScheduleEnd(new Date(future)); + schedule.setScheduleIntervalCount(6); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); + + t = schedule.asTrigger(); + assertNotNull(t); + assertEquals((double) System.currentTimeMillis(), (double) t + .getStartTime().getTime(), 2); // Within 2ms + assertEquals(future, t.getEndTime().getTime()); + assertEquals(DateIntervalTrigger.class, t.getClass()); + assertEquals(6, ((DateIntervalTrigger) t).getRepeatInterval()); + assertEquals(IntervalUnit.HOUR, ((DateIntervalTrigger) t) + .getRepeatIntervalUnit()); + + // Start+end-in-the-past + // (Ignored as the end has passed) + schedule.setScheduleStart(new Date(12345)); + schedule.setScheduleEnd(new Date(54321)); + schedule.setScheduleIntervalCount(null); + schedule.setScheduleIntervalPeriod(null); + + t = schedule.asTrigger(); + assertEquals(null, t); + + // Start+end-in-the-future + // Start is used to decide when to run + // End is used to decide if we missed it! + // (No interval so no repeats so end not needed on trigger) + schedule.setScheduleStart(new Date(12345)); + schedule.setScheduleEnd(new Date(future)); + schedule.setScheduleIntervalCount(null); + schedule.setScheduleIntervalPeriod(null); + + t = schedule.asTrigger(); + assertNotNull(t); + assertEquals(12345, t.getStartTime().getTime()); + assertEquals(null, t.getEndTime()); + assertEquals(SimpleTrigger.class, t.getClass()); + } + + /** + * Tests that the triggers are suitably tweaked based on when the last run + * occured + */ + public void testAsTriggerLastRun() throws Exception + { + long future = System.currentTimeMillis() + 1234567; + long future90mins = System.currentTimeMillis() + 90*60*1000; + long past30mins = System.currentTimeMillis() - 30*60*1000; + long past90mins = System.currentTimeMillis() - 90*60*1000; + long past150mins = System.currentTimeMillis() - 150*60*1000; + + ScheduledPersistedActionImpl schedule = (ScheduledPersistedActionImpl) service + .createSchedule(testAction); + service.saveSchedule(schedule); + Trigger t; + + + // No start date, repeats set, never run + // Will be started ASAP + schedule.setScheduleStart(null); + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(null); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); + schedule.setScheduleIntervalCount(2); + + t = schedule.asTrigger(); + assertNotNull(t); + + assertEquals( + (double) System.currentTimeMillis(), + (double) t.getStartTime().getTime(), 10); // Within 10ms + assertEquals(null, t.getEndTime()); + + + // No start date, repeats set, previously run + // Will be started ASAP + schedule.setScheduleStart(null); + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(new Date(past30mins)); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); + schedule.setScheduleIntervalCount(2); + + t = schedule.asTrigger(); + assertNotNull(t); + + assertEquals( + (double) System.currentTimeMillis(), + (double) t.getStartTime().getTime(), 10); // Within 10ms + assertEquals(null, t.getEndTime()); + + + // Start date in the past, no repeats, never run + // Will be started ASAP + schedule.setScheduleStart(new Date(past30mins)); + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(null); + schedule.setScheduleIntervalPeriod(null); + schedule.setScheduleIntervalCount(null); + + t = schedule.asTrigger(); + assertNotNull(t); + + assertEquals(past30mins, t.getStartTime().getTime()); + assertEquals(null, t.getEndTime()); + + + // Start date in the future, no repeats, never run + // Will be started at the requested time + schedule.setScheduleStart(new Date(future)); + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(null); + schedule.setScheduleIntervalPeriod(null); + schedule.setScheduleIntervalCount(null); + + t = schedule.asTrigger(); + assertNotNull(t); + + assertEquals(future, t.getStartTime().getTime()); + assertEquals(null, t.getEndTime()); + + + // Start date in the past, no repeats, has run since the start date + // Won't be started, as deemed to have already fired + schedule.setScheduleStart(new Date(past90mins)); + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(new Date(past30mins)); + schedule.setScheduleIntervalPeriod(null); + schedule.setScheduleIntervalCount(null); + + t = schedule.asTrigger(); + assertEquals(null, t); + + + // Start date in the past, no repeats, previously run but before the start date + // Will be run ASAP, previous run details will be ignored + schedule.setScheduleStart(new Date(past30mins)); + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(new Date(past90mins)); + schedule.setScheduleIntervalPeriod(null); + schedule.setScheduleIntervalCount(null); + + t = schedule.asTrigger(); + assertNotNull(t); + + assertEquals(past30mins, t.getStartTime().getTime()); + assertEquals(null, t.getEndTime()); + + + // Start date in the future, no repeats, previously run + // Will be started at the requested time + schedule.setScheduleStart(new Date(future)); + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(new Date(past30mins)); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); + schedule.setScheduleIntervalCount(2); + + t = schedule.asTrigger(); + assertNotNull(t); + + assertEquals(future, t.getStartTime().getTime()); + assertEquals(null, t.getEndTime()); + + + // Start date in the past, has repeats, never run + // Will be run ASAP + schedule.setScheduleStart(new Date(past30mins)); + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(null); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); + schedule.setScheduleIntervalCount(2); + + t = schedule.asTrigger(); + assertNotNull(t); + + assertEquals(past30mins, t.getStartTime().getTime()); + assertEquals(null, t.getEndTime()); + + + // Start date in the future, has repeats, never run + // Will be started at the requested time + schedule.setScheduleStart(new Date(future)); + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(null); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); + schedule.setScheduleIntervalCount(2); + + t = schedule.asTrigger(); + assertNotNull(t); + + assertEquals(future, t.getStartTime().getTime()); + assertEquals(null, t.getEndTime()); + + + // Start date in the past, has repeats, last run was before + // the start date + // Will be run ASAP, previous run details will be ignored + schedule.setScheduleStart(new Date(past30mins)); + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(new Date(past90mins)); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); + schedule.setScheduleIntervalCount(2); + + t = schedule.asTrigger(); + assertNotNull(t); + + assertEquals(past30mins, t.getStartTime().getTime()); + assertEquals(null, t.getEndTime()); + + + // Start date in the past, has repeats, run since the start date, + // last run within the repeat interval + // Will run at the next interval, based off the start time + schedule.setScheduleStart(new Date(past150mins)); // 2.5 hours ago + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(new Date(past30mins)); // 0.5 hours ago, schedule worked + schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); + schedule.setScheduleIntervalCount(2); + + t = schedule.asTrigger(); + assertNotNull(t); + + assertEquals(future90mins, t.getStartTime().getTime()); // 4 hours from start time + assertEquals(null, t.getEndTime()); + + + // Start date in the past, has repeats, run since the start date, + // last run over a repeat interval ago + // Will be run ASAP, start date based on real start so that + // the interval is done right + schedule.setScheduleStart(new Date(past150mins)); // 2.5 hours ago + schedule.setScheduleEnd(null); + schedule.setScheduleLastExecutedAt(new Date(past90mins)); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Hour); + schedule.setScheduleIntervalCount(2); + + t = schedule.asTrigger(); + assertNotNull(t); + + assertEquals(past150mins, t.getStartTime().getTime()); // Real start used + assertEquals(future90mins, t.getFireTimeAfter(new Date()).getTime()); // After this, fire 4 hours from start + assertEquals(null, t.getEndTime()); + } + + /** + * Tests that we can create, save, edit, delete etc the scheduled persisted + * actions + */ + public void testCreation() + { + ScheduledPersistedAction schedule = service.createSchedule(testAction); + assertNotNull(schedule); + assertTrue(testAction == schedule.getAction()); + assertEquals(testAction.getNodeRef(), schedule.getAction().getNodeRef()); + + assertNull(schedule.getScheduleStart()); + assertNull(schedule.getScheduleInterval()); + assertNull(schedule.getScheduleIntervalCount()); + assertNull(schedule.getScheduleIntervalPeriod()); + + Date now = new Date(); + schedule.setScheduleStart(now); + assertEquals(now, schedule.getScheduleStart()); + schedule.setScheduleIntervalCount(2); + assertEquals(new Integer(2), schedule.getScheduleIntervalCount()); + schedule + .setScheduleIntervalPeriod(ScheduledPersistedAction.IntervalPeriod.Day); + assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, schedule + .getScheduleIntervalPeriod()); + } + + public void testCreateSaveLoad() throws Exception + { + // create and save schedule + ScheduledPersistedAction schedule = service.createSchedule(testAction); + assertNotNull(schedule); + Date now = new Date(); + schedule.setScheduleStart(now); + schedule.setScheduleIntervalCount(2); + schedule + .setScheduleIntervalPeriod(ScheduledPersistedAction.IntervalPeriod.Day); + + assertNull( + ((ScheduledPersistedActionImpl) schedule).getPersistedAtNodeRef() + ); + service.saveSchedule(schedule); + assertNotNull(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + + // Load it again, should have the same details still + ScheduledPersistedAction retrieved = serviceImpl + .loadPersistentSchedule(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + assertNotNull(retrieved); + assertEquals(testAction.getNodeRef(), retrieved.getAction().getNodeRef()); + assertEquals(now, retrieved.getScheduleStart()); + assertEquals(new Integer(2), retrieved.getScheduleIntervalCount()); + assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, retrieved + .getScheduleIntervalPeriod()); + assertNotNull(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + + // Load a 2nd copy, won't be any changes + ScheduledPersistedAction second = serviceImpl + .loadPersistentSchedule(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + assertNotNull(second); + assertEquals(testAction.getNodeRef(), second.getAction().getNodeRef()); + assertEquals(now, second.getScheduleStart()); + assertEquals(new Integer(2), second.getScheduleIntervalCount()); + assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, second + .getScheduleIntervalPeriod()); + + // Now ensure we can create for an action that didn't have a noderef + // when we started + schedule = service.createSchedule(testAction3); + + assertNull(schedule.getActionNodeRef()); + assertNull(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + + runtimeActionService.createActionNodeRef( + // + testAction3, + ScheduledPersistedActionServiceImpl.SCHEDULED_ACTION_ROOT_NODE_REF, + ContentModel.ASSOC_CONTAINS, QName.createQName("TestAction3")); + + assertNotNull(schedule.getActionNodeRef()); + assertNull(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + + service.saveSchedule(schedule); + + assertNotNull(schedule.getActionNodeRef()); + assertNotNull(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + } + + /** + * Ensures that we can create, save, edit, save load, edit, save, load etc, + * all without problems, and without creating duplicates + */ + public void testEditing() throws Exception + { + // create and save schedule + ScheduledPersistedAction schedule = service.createSchedule(testAction); + assertNotNull(schedule); + Date now = new Date(); + schedule.setScheduleStart(now); + schedule.setScheduleIntervalCount(2); + schedule + .setScheduleIntervalPeriod(ScheduledPersistedAction.IntervalPeriod.Day); + + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + service.saveSchedule(schedule); + txn.commit(); + + // Load and check it hasn't changed + txn = transactionService.getUserTransaction(); + txn.begin(); + ScheduledPersistedAction retrieved = serviceImpl + .loadPersistentSchedule(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + txn.commit(); + + assertNotNull(retrieved); + assertEquals(testAction.getNodeRef(), retrieved.getAction().getNodeRef()); + assertEquals(now, retrieved.getScheduleStart()); + assertEquals(new Integer(2), retrieved.getScheduleIntervalCount()); + assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, retrieved + .getScheduleIntervalPeriod()); + + // Save and re-load without changes + txn = transactionService.getUserTransaction(); + txn.begin(); + service.saveSchedule(schedule); + retrieved = serviceImpl + .loadPersistentSchedule(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + txn.commit(); + + assertNotNull(retrieved); + assertEquals(testAction.getNodeRef(), retrieved.getAction().getNodeRef()); + assertEquals(now, retrieved.getScheduleStart()); + assertEquals(new Integer(2), retrieved.getScheduleIntervalCount()); + assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, retrieved + .getScheduleIntervalPeriod()); + + // Make some small changes + txn = transactionService.getUserTransaction(); + txn.begin(); + retrieved = serviceImpl + .loadPersistentSchedule(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + retrieved.setScheduleIntervalCount(3); + service.saveSchedule(retrieved); + retrieved = serviceImpl + .loadPersistentSchedule(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + txn.commit(); + + assertNotNull(retrieved); + assertEquals(testAction.getNodeRef(), retrieved.getAction().getNodeRef()); + assertEquals(now, retrieved.getScheduleStart()); + assertEquals(new Integer(3), retrieved.getScheduleIntervalCount()); + assertEquals(ScheduledPersistedAction.IntervalPeriod.Day, retrieved + .getScheduleIntervalPeriod()); + + // And some more changes + retrieved + .setScheduleIntervalPeriod(ScheduledPersistedAction.IntervalPeriod.Month); + now = new Date(); + retrieved.setScheduleStart(now); + + txn = transactionService.getUserTransaction(); + txn.begin(); + service.saveSchedule(retrieved); + retrieved = serviceImpl + .loadPersistentSchedule(((ScheduledPersistedActionImpl) schedule) + .getPersistedAtNodeRef()); + txn.commit(); + + assertNotNull(retrieved); + assertEquals(testAction.getNodeRef(), retrieved.getAction().getNodeRef()); + assertEquals(now, retrieved.getScheduleStart()); + assertEquals(new Integer(3), retrieved.getScheduleIntervalCount()); + assertEquals(ScheduledPersistedAction.IntervalPeriod.Month, retrieved + .getScheduleIntervalPeriod()); + } + + /** + * Tests that the listings work, both of all scheduled, and from an action + */ + public void testLoadList() throws Exception + { + assertEquals(0, service.listSchedules().size()); + + // Create + ScheduledPersistedAction schedule1 = service.createSchedule(testAction); + assertNotNull(schedule1); + ScheduledPersistedAction schedule2 = service.createSchedule(testAction2); + assertNotNull(schedule2); + + assertEquals(0, service.listSchedules().size()); + + service.saveSchedule(schedule1); + + assertEquals(1, service.listSchedules().size()); + assertEquals(testAction.getNodeRef(), service.listSchedules().get(0) + .getActionNodeRef()); + + service.saveSchedule(schedule2); + assertEquals(2, service.listSchedules().size()); + } + + public void testLoadFromAction() throws Exception + { + // Create schedule + ScheduledPersistedAction schedule1 = service.createSchedule(testAction); + assertNotNull(schedule1); + service.saveSchedule(schedule1); + + // retrieve schedule for action which doesn't have schedule + ScheduledPersistedAction retrieved = service.getSchedule(testAction2); + assertNull(retrieved); + + // and from one which does + retrieved = service.getSchedule(testAction); + assertNotNull(retrieved); + assertEquals(testAction.getNodeRef(), retrieved.getActionNodeRef()); + } + + /** + * Tests that the startup registering works properly + */ + public void testStartup() throws Exception + { // Startup with none there, nothing happens - // TODO + assertEquals( + 0, + scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length + ); + assertEquals( + 0, + service.listSchedules().size() + ); - // Add one manually, startup, see it is registered - // TODO + bootstrap.onBootstrap(null); + assertEquals( + 0, + scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length + ); + assertEquals( + 0, + service.listSchedules().size() + ); + + + // Manually add a scheduled action + // Does a bit of faffing to have it not in Quartz initially + long future = System.currentTimeMillis() + 1234567; + ScheduledPersistedAction schedule = service.createSchedule(testAction); + schedule.setScheduleStart(new Date(future)); + service.saveSchedule(schedule); + + ((ScheduledPersistedActionServiceImpl)ctx.getBean("scheduledPersistedActionService")) + .removeFromScheduler( (ScheduledPersistedActionImpl)schedule ); + + assertEquals( + 0, + scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length + ); + assertEquals( + 1, + service.listSchedules().size() + ); + + // Now do the bootstrap, and see it get registered + bootstrap.onBootstrap(null); - // ==================================== - // Startup related re-scheduling parts - // ==================================== - - // No start date, repeats set, never run - // TODO - - // No start date, repeats set, previously run - // TODO - - // Start date, no repeats, never run - // TODO - - // Start date, no repeats, previously run - // TODO - - // Start date, has repeats, never run - // TODO - - // Start date, has repeats, previously run - // TODO + assertEquals( + 1, + scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length + ); + assertEquals( + 1, + service.listSchedules().size() + ); } - /** - * Ensures that deletion works correctly - */ - public void testDeletion() throws Exception - { - // Delete does nothing if not persisted - assertEquals(0, service.listSchedules().size()); - ScheduledPersistedAction schedule1 = service.createSchedule(testAction); - assertEquals(0, service.listSchedules().size()); + /** + * Ensures that deletion works correctly + */ + public void testDeletion() throws Exception + { + // Delete does nothing if not persisted + assertEquals(0, service.listSchedules().size()); + ScheduledPersistedAction schedule1 = service.createSchedule(testAction); + assertEquals(0, service.listSchedules().size()); - service.deleteSchedule(schedule1); - assertEquals(0, service.listSchedules().size()); + service.deleteSchedule(schedule1); + assertEquals(0, service.listSchedules().size()); - // Create and save two - ScheduledPersistedAction schedule2 = service.createSchedule(testAction2); - service.saveSchedule(schedule1); - service.saveSchedule(schedule2); - assertEquals(2, service.listSchedules().size()); - NodeRef schedule1NodeRef = ((ScheduledPersistedActionImpl)schedule1).getPersistedAtNodeRef(); - NodeRef schedule2NodeRef = ((ScheduledPersistedActionImpl)schedule2).getPersistedAtNodeRef(); - - // Both should have the relationship - assertEquals(1, nodeService.getTargetAssocs(schedule1NodeRef, RegexQNamePattern.MATCH_ALL).size()); - assertEquals(1, nodeService.getTargetAssocs(schedule2NodeRef, RegexQNamePattern.MATCH_ALL).size()); - assertEquals(1, nodeService.getSourceAssocs(testAction.getNodeRef(), RegexQNamePattern.MATCH_ALL).size()); - assertEquals(1, nodeService.getSourceAssocs(testAction2.getNodeRef(), RegexQNamePattern.MATCH_ALL).size()); - - // Delete one - the correct one goes! - service.deleteSchedule(schedule2); - assertEquals(1, service.listSchedules().size()); - assertEquals(testAction.getNodeRef(), service.listSchedules().get(0).getActionNodeRef()); - assertNotNull(serviceImpl.loadPersistentSchedule(schedule1NodeRef)); - assertNull(serviceImpl.loadPersistentSchedule(schedule2NodeRef)); - assertNotNull(service.getSchedule(testAction)); - assertNull(service.getSchedule(testAction2)); - - // Ensure that the relationship went - assertEquals(1, nodeService.getTargetAssocs(schedule1NodeRef, RegexQNamePattern.MATCH_ALL).size()); - assertEquals(1, nodeService.getSourceAssocs(testAction.getNodeRef(), RegexQNamePattern.MATCH_ALL).size()); - assertEquals(0, nodeService.getSourceAssocs(testAction2.getNodeRef(), RegexQNamePattern.MATCH_ALL).size()); - - // Re-delete already deleted, no change - service.deleteSchedule(schedule2); - assertEquals(1, service.listSchedules().size()); - assertEquals(testAction.getNodeRef(), service.listSchedules().get(0).getActionNodeRef()); - assertNotNull(serviceImpl.loadPersistentSchedule(schedule1NodeRef)); - assertNull(serviceImpl.loadPersistentSchedule(schedule2NodeRef)); - assertNotNull(service.getSchedule(testAction)); - assertNull(service.getSchedule(testAction2)); + // Create and save two + ScheduledPersistedAction schedule2 = service.createSchedule(testAction2); + service.saveSchedule(schedule1); + service.saveSchedule(schedule2); + assertEquals(2, service.listSchedules().size()); + NodeRef schedule1NodeRef = ((ScheduledPersistedActionImpl) schedule1) + .getPersistedAtNodeRef(); + NodeRef schedule2NodeRef = ((ScheduledPersistedActionImpl) schedule2) + .getPersistedAtNodeRef(); - // Delete the 2nd - service.deleteSchedule(schedule1); - assertEquals(0, service.listSchedules().size()); - assertNull(serviceImpl.loadPersistentSchedule(schedule1NodeRef)); - assertNull(serviceImpl.loadPersistentSchedule(schedule2NodeRef)); - assertNull(service.getSchedule(testAction)); - assertNull(service.getSchedule(testAction2)); - - // Ensure that the relationship went - assertEquals(0, nodeService.getSourceAssocs(testAction.getNodeRef(), RegexQNamePattern.MATCH_ALL).size()); - assertEquals(0, nodeService.getSourceAssocs(testAction2.getNodeRef(), RegexQNamePattern.MATCH_ALL).size()); - - // Can add back in again after being deleted - service.saveSchedule(schedule1); - assertEquals(1, service.listSchedules().size()); - assertEquals(testAction.getNodeRef(), service.listSchedules().get(0).getActionNodeRef()); - assertNotNull(service.getSchedule(testAction)); - assertNull(service.getSchedule(testAction2)); - - // If we delete the action, then we have an orphaned schedule - UserTransaction txn = transactionService.getUserTransaction(); - txn.begin(); - nodeService.deleteNode(testAction.getNodeRef()); - txn.commit(); - - assertEquals(1, service.listSchedules().size()); - assertEquals(1, service.listSchedules().size()); - assertEquals(null, service.listSchedules().get(0).getAction()); - assertEquals(null, service.listSchedules().get(0).getActionNodeRef()); - } - - /** - * Tests that things get properly injected onto the job bean - */ - public void testJobBeanInjection() throws Exception - { - // This test needs the scheduler running properly - scheduler.start(); - - // The job should run almost immediately - Job job = new TestJob(); - JobDetail details = new JobDetail("ThisIsATest", null, job.getClass()); - Trigger now = new SimpleTrigger("TestTrigger", new Date(1)); - now.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); + // Both should have the relationship + assertEquals(1, nodeService.getTargetAssocs(schedule1NodeRef, + RegexQNamePattern.MATCH_ALL).size()); + assertEquals(1, nodeService.getTargetAssocs(schedule2NodeRef, + RegexQNamePattern.MATCH_ALL).size()); + assertEquals(1, nodeService.getSourceAssocs(testAction.getNodeRef(), + RegexQNamePattern.MATCH_ALL).size()); + assertEquals(1, nodeService.getSourceAssocs(testAction2.getNodeRef(), + RegexQNamePattern.MATCH_ALL).size()); - Scheduler scheduler = (Scheduler) ctx.getBean("schedulerFactory"); - scheduler.scheduleJob(details, now); + // Delete one - the correct one goes! + service.deleteSchedule(schedule2); + assertEquals(1, service.listSchedules().size()); + assertEquals(testAction.getNodeRef(), service.listSchedules().get(0) + .getActionNodeRef()); + assertNotNull(serviceImpl.loadPersistentSchedule(schedule1NodeRef)); + assertNull(serviceImpl.loadPersistentSchedule(schedule2NodeRef)); + assertNotNull(service.getSchedule(testAction)); + assertNull(service.getSchedule(testAction2)); - // Allow it to run - for (int i = 0; i < 20; i++) - { - if (!TestJob.ran) - Thread.sleep(50); - } + // Ensure that the relationship went + assertEquals(1, nodeService.getTargetAssocs(schedule1NodeRef, + RegexQNamePattern.MATCH_ALL).size()); + assertEquals(1, nodeService.getSourceAssocs(testAction.getNodeRef(), + RegexQNamePattern.MATCH_ALL).size()); + assertEquals(0, nodeService.getSourceAssocs(testAction2.getNodeRef(), + RegexQNamePattern.MATCH_ALL).size()); - // Ensure it ran, and it got a copy of the context - assertEquals(true, TestJob.ran); - assertEquals(true, TestJob.gotContext); - } + // Re-delete already deleted, no change + service.deleteSchedule(schedule2); + assertEquals(1, service.listSchedules().size()); + assertEquals(testAction.getNodeRef(), service.listSchedules().get(0) + .getActionNodeRef()); + assertNotNull(serviceImpl.loadPersistentSchedule(schedule1NodeRef)); + assertNull(serviceImpl.loadPersistentSchedule(schedule2NodeRef)); + assertNotNull(service.getSchedule(testAction)); + assertNull(service.getSchedule(testAction2)); - /** - * Tests that things actually get run correctly. - * Each sub-test runs in its own transaction - */ - public void testExecution() throws Exception - { - final SleepActionExecuter sleepActionExec = (SleepActionExecuter) ctx.getBean(SleepActionExecuter.NAME); - sleepActionExec.resetTimesExecuted(); - sleepActionExec.setSleepMs(1); + // Delete the 2nd + service.deleteSchedule(schedule1); + assertEquals(0, service.listSchedules().size()); + assertNull(serviceImpl.loadPersistentSchedule(schedule1NodeRef)); + assertNull(serviceImpl.loadPersistentSchedule(schedule2NodeRef)); + assertNull(service.getSchedule(testAction)); + assertNull(service.getSchedule(testAction2)); - // This test needs the scheduler running properly - scheduler.start(); - - // Until the schedule is persisted, nothing will happen - ScheduledPersistedAction schedule = service.createSchedule(testAction); - assertEquals(0, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); + // Ensure that the relationship went + assertEquals(0, nodeService.getSourceAssocs(testAction.getNodeRef(), + RegexQNamePattern.MATCH_ALL).size()); + assertEquals(0, nodeService.getSourceAssocs(testAction2.getNodeRef(), + RegexQNamePattern.MATCH_ALL).size()); - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - public Void execute() throws Throwable { - - // A job due to start in 1 second, and run once - ScheduledPersistedAction schedule = service.createSchedule(testAction); - schedule.setScheduleStart(new Date(System.currentTimeMillis() + 1000)); - assertNull(schedule.getScheduleInterval()); - assertNull(schedule.getScheduleIntervalCount()); - assertNull(schedule.getScheduleIntervalPeriod()); - assertNull(schedule.getScheduleLastExecutedAt()); + // Can add back in again after being deleted + service.saveSchedule(schedule1); + assertEquals(1, service.listSchedules().size()); + assertEquals(testAction.getNodeRef(), service.listSchedules().get(0) + .getActionNodeRef()); + assertNotNull(service.getSchedule(testAction)); + assertNull(service.getSchedule(testAction2)); - System.out.println("Job starts in 1 second, no repeat..."); - service.saveSchedule(schedule); - - return null; - } - }, false, true); + // If we delete the action, then we have an orphaned schedule + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + nodeService.deleteNode(testAction.getNodeRef()); + txn.commit(); - // Check it went in - assertEquals(1, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); + assertEquals(1, service.listSchedules().size()); + assertEquals(1, service.listSchedules().size()); + assertEquals(null, service.listSchedules().get(0).getAction()); + assertEquals(null, service.listSchedules().get(0).getActionNodeRef()); + } - // Let it run - Thread.sleep(2000); + /** + * Tests that things get properly injected onto the job bean + */ + public void testJobBeanInjection() throws Exception + { + // This test needs the scheduler running properly + scheduler.start(); - // Ensure it did properly run the once - assertEquals(1, sleepActionExec.getTimesExecuted()); + // The job should run almost immediately + Job job = new TestJob(); + JobDetail details = new JobDetail("ThisIsATest", null, job.getClass()); + Trigger now = new SimpleTrigger("TestTrigger", new Date(1)); + now.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); - // Should have removed itself now the schedule is over - assertEquals(0, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); - + Scheduler scheduler = (Scheduler) ctx.getBean("schedulerFactory"); + scheduler.scheduleJob(details, now); - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - public Void execute() throws Throwable { - - // Ensure it was tagged with when it ran - ScheduledPersistedAction schedule = service.getSchedule(testAction); - assertEquals((double)System.currentTimeMillis(), (double)schedule.getScheduleLastExecutedAt().getTime(), 2500); // Within 2.5 secs + // Allow it to run + for (int i = 0; i < 20; i++) { + if (!TestJob.ran) + Thread.sleep(50); + } - // Zap it - service.deleteSchedule(schedule); - assertEquals(0, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); - - return null; - } - }, false, true); + // Ensure it ran, and it got a copy of the context + assertEquals(true, TestJob.ran); + assertEquals(true, TestJob.gotContext); + } - - // ========================== + /** + * Tests that things actually get run correctly. Each sub-test runs in its + * own transaction + */ + public void testExecution() throws Exception + { + final SleepActionExecuter sleepActionExec = (SleepActionExecuter) ctx + .getBean(SleepActionExecuter.NAME); + sleepActionExec.resetTimesExecuted(); + sleepActionExec.setSleepMs(1); - - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - public Void execute() throws Throwable { - - // A job that runs every 2 seconds, for the next 3.5 seconds - // (Should get to run twice, now and @2 secs) - ScheduledPersistedAction 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("2Second", schedule.getScheduleInterval()); - - // Reset count - sleepActionExec.resetTimesExecuted(); - assertEquals(0, sleepActionExec.getTimesExecuted()); - - System.out.println("Job starts now, repeats twice @ 2s"); - service.saveSchedule(schedule); - - return null; - } - }, false, true); - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - public Void execute() throws Throwable { - Thread.sleep(4500); - - ScheduledPersistedAction schedule = service.getSchedule(testAction); - - // Ensure it did properly run two times - // (Depending on timing of tests, might actually slip in 3 runs) - if(sleepActionExec.getTimesExecuted() == 3) { - assertEquals(3, sleepActionExec.getTimesExecuted()); - } else { - assertEquals(2, sleepActionExec.getTimesExecuted()); - } - - // Zap it - service.deleteSchedule(schedule); - assertEquals(0, scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); - - return null; - } - }, false, true); + // This test needs the scheduler running properly + scheduler.start(); - - // ========================== + // Until the schedule is persisted, nothing will happen + ScheduledPersistedAction schedule = service.createSchedule(testAction); + assertEquals( + 0, + scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length + ); - - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - public Void execute() throws Throwable { - - // A job that starts in 2 seconds time, and runs - // every second until we kill it - ScheduledPersistedAction schedule = service.createSchedule(testAction); - schedule.setScheduleStart(new Date(System.currentTimeMillis() + 2000)); - schedule.setScheduleIntervalCount(1); - schedule.setScheduleIntervalPeriod(IntervalPeriod.Second); - assertEquals("1Second", schedule.getScheduleInterval()); - - // Reset count - sleepActionExec.resetTimesExecuted(); - assertEquals(0, sleepActionExec.getTimesExecuted()); - - System.out.println("Job starts in 2s, repeats @ 1s"); - service.saveSchedule(schedule); - - return null; - } - }, false, true); - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - public Void execute() throws Throwable { - - // Let it run a few times - Thread.sleep(5000); - - // Zap it - should still be live - ScheduledPersistedAction schedule = service.getSchedule(testAction); - 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); - - return null; - } - }, false, true); - } + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() { + public Void execute() throws Throwable { - /** - * 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 testMultipleExecutions() throws Exception - { - final SleepActionExecuter sleepActionExec = (SleepActionExecuter) ctx.getBean(SleepActionExecuter.NAME); - sleepActionExec.resetTimesExecuted(); - sleepActionExec.setSleepMs(1); + // A job due to start in 1 second, and run once + ScheduledPersistedAction schedule = service + .createSchedule(testAction); + schedule.setScheduleStart(new Date( + System.currentTimeMillis() + 1000)); + assertNull(schedule.getScheduleInterval()); + assertNull(schedule.getScheduleIntervalCount()); + assertNull(schedule.getScheduleIntervalPeriod()); + assertNull(schedule.getScheduleLastExecutedAt()); - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - public Void execute() throws Throwable { - - // Create one that starts running in 2 seconds, runs every 2 seconds - // until 9 seconds are up (will run 4 times) - ScheduledPersistedActionImpl scheduleA = (ScheduledPersistedActionImpl)service.createSchedule(testAction); - scheduleA.setScheduleStart(new Date(System.currentTimeMillis()+2000)); - scheduleA.setScheduleEnd(new Date(System.currentTimeMillis()+9000)); - scheduleA.setScheduleIntervalCount(2); - scheduleA.setScheduleIntervalPeriod(IntervalPeriod.Second); - service.saveSchedule(scheduleA); - + System.out.println("Job starts in 1 second, no repeat..."); + service.saveSchedule(schedule); - // Create one that starts running now, every second until 9.5 seconds - // are up (will run 9-10 times) - ScheduledPersistedActionImpl scheduleB = (ScheduledPersistedActionImpl)service.createSchedule(testAction2); - scheduleB.setScheduleStart(new Date(System.currentTimeMillis())); - scheduleB.setScheduleEnd(new Date(System.currentTimeMillis()+9500)); - scheduleB.setScheduleIntervalCount(1); - scheduleB.setScheduleIntervalPeriod(IntervalPeriod.Second); - service.saveSchedule(scheduleB); - - return null; - } - }, false, true); + return null; + } + }, false, true); - - // Set them going - scheduler.start(); - + // Check it went in + assertEquals( + 1, + scheduler + .getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); - // Wait for 10 seconds for them to run and finish - Thread.sleep(10*1000); + // Let it run + Thread.sleep(2000); - - // Check that they really did run properly - transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - public Void execute() throws Throwable { - - ScheduledPersistedAction scheduleA = service.getSchedule(testAction); - ScheduledPersistedAction scheduleB = service.getSchedule(testAction2); - - // Both should have last run at some point in the last second or two - assertEquals((double)System.currentTimeMillis(), (double)scheduleA.getScheduleLastExecutedAt().getTime(), 2500); - assertEquals((double)System.currentTimeMillis(), (double)scheduleB.getScheduleLastExecutedAt().getTime(), 2500); - - // A should have run ~4 times - // B should have run ~9 times - assertEquals("Didn't run enough - " + sleepActionExec.getTimesExecuted(), true, sleepActionExec - .getTimesExecuted() >= 11); - assertEquals("Ran too much - " + sleepActionExec.getTimesExecuted(), true, - sleepActionExec.getTimesExecuted() < 16); - + // Ensure it did properly run the once + assertEquals(1, sleepActionExec.getTimesExecuted()); - return null; - } - }, false, true); - } + // Should have removed itself now the schedule is over + assertEquals( + 0, + scheduler + .getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); - // ============================================================================ + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() { + public Void execute() throws Throwable { - /** - * For unit testing only - not thread safe! - */ - public static class TestJob implements Job, ApplicationContextAware - { - private static boolean gotContext = false; - private static boolean ran = false; + // Ensure it was tagged with when it ran + ScheduledPersistedAction schedule = service + .getSchedule(testAction); + assertEquals( + (double) System.currentTimeMillis(), + (double) schedule.getScheduleLastExecutedAt().getTime(), + 2500); // Within 2.5 secs - public TestJob() - { - gotContext = false; - ran = false; - } + // Zap it + service.deleteSchedule(schedule); + assertEquals( + 0, + scheduler + .getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length); - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException - { - gotContext = true; - } + return null; + } + }, false, true); - public void execute(JobExecutionContext paramJobExecutionContext) throws JobExecutionException - { - ran = true; - } - } + // ========================== - protected static class TestAction extends ActionImpl - { - protected TestAction(Action action) - { - super(action); - } - } + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() { + public Void execute() throws Throwable { + + // A job that runs every 2 seconds, for the next 3.5 seconds + // (Should get to run twice, now and @2 secs) + ScheduledPersistedAction schedule = service + .createSchedule(testAction); + schedule.setScheduleStart(new Date(System.currentTimeMillis()-50)); + ((ScheduledPersistedActionImpl) schedule) + .setScheduleEnd(new Date( + System.currentTimeMillis() + 3500)); + schedule.setScheduleIntervalCount(2); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Second); + assertEquals("2Second", schedule.getScheduleInterval()); + + // Reset count + sleepActionExec.resetTimesExecuted(); + assertEquals(0, sleepActionExec.getTimesExecuted()); + + service.saveSchedule(schedule); + System.out.println( + "Job " + + ((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef() + + " starts now, repeats twice @ 2s" + ); + + return null; + } + }, false, true); + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() { + public Void execute() throws Throwable { + Thread.sleep(4250); + + ScheduledPersistedAction schedule = service + .getSchedule(testAction); + + // Ensure it did properly run two times + // (Depending on timing of tests, might actually slip in 3 + // runs) + if (sleepActionExec.getTimesExecuted() == 3) { + assertEquals(3, sleepActionExec.getTimesExecuted()); + } else { + assertEquals(2, sleepActionExec.getTimesExecuted()); + } + + // Zap it + service.deleteSchedule(schedule); + assertEquals( + 0, + scheduler.getJobNames(ScheduledPersistedActionServiceImpl.SCHEDULER_GROUP).length + ); + + return null; + } + }, false, true); + + // ========================== + + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() { + public Void execute() throws Throwable { + + // A job that starts in 2 seconds time, and runs + // every second until we kill it + ScheduledPersistedAction schedule = service + .createSchedule(testAction); + schedule.setScheduleStart(new Date( + System.currentTimeMillis() + 2000)); + schedule.setScheduleIntervalCount(1); + schedule.setScheduleIntervalPeriod(IntervalPeriod.Second); + assertEquals("1Second", schedule.getScheduleInterval()); + + // Reset count + sleepActionExec.resetTimesExecuted(); + assertEquals(0, sleepActionExec.getTimesExecuted()); + + System.out.println("Job starts in 2s, repeats @ 1s"); + service.saveSchedule(schedule); + + return null; + } + }, false, true); + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() { + public Void execute() throws Throwable { + + // Let it run a few times + Thread.sleep(5000); + + // Zap it - should still be live + ScheduledPersistedAction schedule = service + .getSchedule(testAction); + 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); + + return null; + } + }, false, true); + } + + /** + * 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 testMultipleExecutions() throws Exception + { + final SleepActionExecuter sleepActionExec = (SleepActionExecuter) ctx + .getBean(SleepActionExecuter.NAME); + sleepActionExec.resetTimesExecuted(); + sleepActionExec.setSleepMs(1); + + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() { + public Void execute() throws Throwable { + + // Create one that starts running in 2 seconds, runs every 2 + // seconds + // until 9 seconds are up (will run 4 times) + ScheduledPersistedActionImpl scheduleA = (ScheduledPersistedActionImpl) service + .createSchedule(testAction); + scheduleA.setScheduleStart(new Date(System + .currentTimeMillis() + 2000)); + scheduleA.setScheduleEnd(new Date( + System.currentTimeMillis() + 9000)); + scheduleA.setScheduleIntervalCount(2); + scheduleA.setScheduleIntervalPeriod(IntervalPeriod.Second); + service.saveSchedule(scheduleA); + + // Create one that starts running now, every second until 9.5 + // seconds + // are up (will run 9-10 times) + ScheduledPersistedActionImpl scheduleB = (ScheduledPersistedActionImpl) service + .createSchedule(testAction2); + scheduleB.setScheduleStart(new Date(System + .currentTimeMillis())); + scheduleB.setScheduleEnd(new Date( + System.currentTimeMillis() + 9500)); + scheduleB.setScheduleIntervalCount(1); + scheduleB.setScheduleIntervalPeriod(IntervalPeriod.Second); + service.saveSchedule(scheduleB); + + return null; + } + }, false, true); + + // Set them going + scheduler.start(); + + // Wait for 10 seconds for them to run and finish + Thread.sleep(10 * 1000); + + // Check that they really did run properly + transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() { + public Void execute() throws Throwable { + + ScheduledPersistedAction scheduleA = service + .getSchedule(testAction); + ScheduledPersistedAction scheduleB = service + .getSchedule(testAction2); + + // Both should have last run at some point in the last second + // or two + assertEquals((double) System.currentTimeMillis(), + (double) scheduleA.getScheduleLastExecutedAt() + .getTime(), 2500); + assertEquals((double) System.currentTimeMillis(), + (double) scheduleB.getScheduleLastExecutedAt() + .getTime(), 2500); + + // A should have run ~4 times + // B should have run ~9 times + assertEquals("Didn't run enough - " + + sleepActionExec.getTimesExecuted(), true, + sleepActionExec.getTimesExecuted() >= 11); + assertEquals("Ran too much - " + + sleepActionExec.getTimesExecuted(), true, + sleepActionExec.getTimesExecuted() < 16); + + return null; + } + }, false, true); + } + + // ============================================================================ + + /** + * For unit testing only - not thread safe! + */ + public static class TestJob implements Job, ApplicationContextAware + { + private static boolean gotContext = false; + private static boolean ran = false; + + public TestJob() + { + gotContext = false; + ran = false; + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + gotContext = true; + } + + public void execute(JobExecutionContext paramJobExecutionContext) throws JobExecutionException + { + ran = true; + } + } + + protected static class TestAction extends ActionImpl + { + protected TestAction(Action action) + { + super(action); + } + } }