diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 00ea36bafd..0e40a6109b 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -602,8 +602,9 @@ - + + diff --git a/config/alfresco/public-services-context.xml b/config/alfresco/public-services-context.xml index 3c61f923ac..ef0490b80f 100644 --- a/config/alfresco/public-services-context.xml +++ b/config/alfresco/public-services-context.xml @@ -1419,6 +1419,38 @@ + + + org.alfresco.service.cmr.action.scheduled.ScheduledPersistedActionService + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + org.alfresco.service.cmr.security.PublicServiceAccessService diff --git a/source/java/org/alfresco/repo/action/ActionModel.java b/source/java/org/alfresco/repo/action/ActionModel.java index 48d0f7401b..ddb711999b 100644 --- a/source/java/org/alfresco/repo/action/ActionModel.java +++ b/source/java/org/alfresco/repo/action/ActionModel.java @@ -51,6 +51,12 @@ public interface ActionModel static final QName ASPECT_ACTIONS = QName.createQName(ACTION_MODEL_URI, "actions"); static final QName ASSOC_ACTION_FOLDER = QName.createQName(ACTION_MODEL_URI, "actionFolder"); + static final QName TYPE_ACTION_SCHEDULE = QName.createQName(ACTION_MODEL_URI, "actionSchedule"); + static final QName PROP_START_DATE = QName.createQName(ACTION_MODEL_URI, "startDate"); + static final QName PROP_INTERVAL_COUNT = QName.createQName(ACTION_MODEL_URI, "intervalCount"); + static final QName PROP_INTERVAL_PERIOD = QName.createQName(ACTION_MODEL_URI, "intervalPeriod"); + static final QName ASSOC_SCHEDULED_ACTION = QName.createQName(ACTION_MODEL_URI, "scheduledAction"); + //static final QName ASPECT_ACTIONABLE = QName.createQName(ACTION_MODEL_URI, "actionable"); //static final QName ASSOC_SAVED_ACTION_FOLDERS = QName.createQName(ACTION_MODEL_URI, "savedActionFolders"); //static final QName TYPE_SAVED_ACTION_FOLDER = QName.createQName(ACTION_MODEL_URI, "savedactionfolder"); diff --git a/source/java/org/alfresco/repo/action/actionModel.xml b/source/java/org/alfresco/repo/action/actionModel.xml index 7dfd586b15..71c186f4ab 100644 --- a/source/java/org/alfresco/repo/action/actionModel.xml +++ b/source/java/org/alfresco/repo/action/actionModel.xml @@ -235,11 +235,12 @@ true - M - W - D - h - m + Month + Week + Day + Hour + Minute + Second @@ -248,6 +249,10 @@ + + false + false + act:action true diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java index 53d9873278..08b7616f38 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java @@ -75,7 +75,7 @@ public class ScheduledPersistedActionImpl implements ScheduledPersistedAction /** Get where the action lives */ public NodeRef getActionNodeRef() { - return action.getNodeRef(); + return action == null ? null : action.getNodeRef(); } /** @@ -157,8 +157,8 @@ public class ScheduledPersistedActionImpl implements ScheduledPersistedAction /** - * Returns the interval in a form like 1D (1 day) - * or 2h (2 hours), or null if a period+count + * Returns the interval in a form like 1Day (1 day) + * or 2Hour (2 hours), or null if a period+count * hasn't been set */ public String getScheduleInterval() @@ -167,7 +167,7 @@ public class ScheduledPersistedActionImpl implements ScheduledPersistedAction { return null; } - return intervalCount.toString() + intervalPeriod.getLetter(); + return intervalCount.toString() + intervalPeriod.name(); } /** diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java index 7459dfb5b4..61324135b3 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java @@ -20,6 +20,7 @@ package org.alfresco.repo.action.scheduled; import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -29,14 +30,22 @@ 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.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; 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.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.Job; @@ -66,7 +75,7 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc protected static NodeRef SCHEDULED_ACTION_ROOT_NODE_REF; protected static final Set ACTION_TYPES = new HashSet(Arrays - .asList(new QName[] { ActionModel.TYPE_ACTION })); // TODO + .asList(new QName[] { ActionModel.TYPE_ACTION_SCHEDULE })); protected static final String SCHEDULER_GROUP = "PersistedActions"; @@ -113,7 +122,7 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc NodeRef dataDictionary = startupNodeService.getChildByName( repositoryHelper.getCompanyHome(), ContentModel.ASSOC_CONTAINS, - "data dictionary" + "Data Dictionary" ); SCHEDULED_ACTION_ROOT_NODE_REF = startupNodeService.getChildByName( dataDictionary, @@ -151,40 +160,133 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc */ public void saveSchedule(ScheduledPersistedAction schedule) { + ScheduledPersistedActionImpl scheduleImpl = (ScheduledPersistedActionImpl)schedule; + // Remove if already there - removeFromScheduler((ScheduledPersistedActionImpl) schedule); + removeFromScheduler(scheduleImpl); - // TODO Create the node + relationship if not already there - - // Save to the repo - // TODO Persist details + if (scheduleImpl.getPersistedAtNodeRef() == null) + { + // if not already persisted, create the persistent schedule + createPersistentSchedule(scheduleImpl); + } + // update the persistent schedule with schedule properties + updatePersistentSchedule(scheduleImpl); + // Add to the scheduler again - addToScheduler((ScheduledPersistedActionImpl) schedule); + addToScheduler(scheduleImpl); } + private void createPersistentSchedule(ScheduledPersistedActionImpl schedule) + { + ChildAssociationRef childAssoc = nodeService.createNode(SCHEDULED_ACTION_ROOT_NODE_REF, + ContentModel.ASSOC_CONTAINS, QName.createQName(GUID.generate()), + ActionModel.TYPE_ACTION_SCHEDULE); + schedule.setPersistedAtNodeRef(childAssoc.getChildRef()); + } + + private void updatePersistentSchedule(ScheduledPersistedActionImpl schedule) + { + NodeRef nodeRef = schedule.getPersistedAtNodeRef(); + if (nodeRef == null) + throw new IllegalStateException("Must be persisted first"); + + // update schedule properties + nodeService.setProperty(nodeRef, ActionModel.PROP_START_DATE, schedule.getScheduleStart()); + nodeService.setProperty(nodeRef, ActionModel.PROP_INTERVAL_COUNT, schedule.getScheduleIntervalCount()); + IntervalPeriod period = schedule.getScheduleIntervalPeriod(); + nodeService.setProperty(nodeRef, ActionModel.PROP_INTERVAL_PERIOD, period == null ? null : period.name()); + + // update scheduled action (represented as an association) + // NOTE: can only associate to a single action from a schedule (as specified by the action model) + + // update association to reflect updated schedule + AssociationRef actionAssoc = findActionAssociationFromSchedule(nodeRef); + NodeRef actionNodeRef = schedule.getActionNodeRef(); + if (actionNodeRef == null) + { + if (actionAssoc != null) + { + // remove associated action + nodeService.removeAssociation(actionAssoc.getSourceRef(), actionAssoc.getTargetRef(), actionAssoc.getTypeQName()); + } + } + else + { + if (actionAssoc == null) + { + // create associated action + nodeService.createAssociation(nodeRef, actionNodeRef, ActionModel.ASSOC_SCHEDULED_ACTION); + } + else if (!actionAssoc.getTargetRef().equals(actionNodeRef)) + { + // associated action has changed... first remove existing association + nodeService.removeAssociation(actionAssoc.getSourceRef(), actionAssoc.getTargetRef(), actionAssoc.getTypeQName()); + nodeService.createAssociation(nodeRef, actionNodeRef, ActionModel.ASSOC_SCHEDULED_ACTION); + } + } + } + /** * Removes the schedule for the action, and cancels future executions of it. * The persisted action is unchanged. */ public void deleteSchedule(ScheduledPersistedAction schedule) { + ScheduledPersistedActionImpl scheduleImpl = (ScheduledPersistedActionImpl)schedule; // Remove from the scheduler - removeFromScheduler((ScheduledPersistedActionImpl) schedule); + removeFromScheduler(scheduleImpl); // Now remove from the repo - // TODO + deletePersistentSchedule(scheduleImpl); } + private void deletePersistentSchedule(ScheduledPersistedActionImpl schedule) + { + NodeRef nodeRef = schedule.getPersistedAtNodeRef(); + if (nodeRef == null) + return; + + // NOTE: this will also cascade delete action association + nodeService.deleteNode(nodeRef); + + schedule.setPersistedAtNodeRef(null); + } + /** * Returns the schedule for the specified action, or null if it isn't * currently scheduled. */ public ScheduledPersistedAction getSchedule(Action persistedAction) { - // TODO look for a relationship of the special type from - // the action, then if we find it load the schedule via it - return null; + NodeRef nodeRef = persistedAction.getNodeRef(); + if (nodeRef == null) + { + // action is not persistent + return null; + } + + // locate associated schedule for action + List assocs = nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL); + AssociationRef scheduledAssoc = null; + for (AssociationRef assoc : assocs) + { + if (ActionModel.ASSOC_SCHEDULED_ACTION.equals(assoc.getTypeQName())) + { + scheduledAssoc = assoc; + break; + } + } + + if (scheduledAssoc == null) + { + // there is no associated schedule + return null; + } + + // load the scheduled action + return loadPersistentSchedule(scheduledAssoc.getSourceRef()); } /** @@ -204,15 +306,56 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc childAssocs.size()); for (ChildAssociationRef actionAssoc : childAssocs) { - // TODO - // Action nextAction = - // runtimeActionService.createAction(actionAssoc.getChildRef()); - // renderingActions.add(new ReplicationDefinitionImpl(nextAction)); + ScheduledPersistedActionImpl scheduleImpl = loadPersistentSchedule(actionAssoc.getChildRef()); + scheduledActions.add(scheduleImpl); } return scheduledActions; } + protected ScheduledPersistedActionImpl loadPersistentSchedule(NodeRef schedule) + { + if (!nodeService.exists(schedule)) + return null; + + // create action + Action action = null; + AssociationRef actionAssoc = findActionAssociationFromSchedule(schedule); + if (actionAssoc != null) + { + action = runtimeActionService.createAction(actionAssoc.getTargetRef()); + } + + // create schedule + ScheduledPersistedActionImpl scheduleImpl = new ScheduledPersistedActionImpl(action); + scheduleImpl.setPersistedAtNodeRef(schedule); + scheduleImpl.setScheduleStart((Date)nodeService.getProperty(schedule, ActionModel.PROP_START_DATE)); + scheduleImpl.setScheduleIntervalCount((Integer)nodeService.getProperty(schedule, ActionModel.PROP_INTERVAL_COUNT)); + String period = (String)nodeService.getProperty(schedule, ActionModel.PROP_INTERVAL_PERIOD); + if (period != null) + { + scheduleImpl.setScheduleIntervalPeriod(IntervalPeriod.valueOf(period)); + } + + return scheduleImpl; + } + + private AssociationRef findActionAssociationFromSchedule(NodeRef schedule) + { + List assocs = nodeService.getTargetAssocs(schedule, RegexQNamePattern.MATCH_ALL); + AssociationRef actionAssoc = null; + for (AssociationRef assoc : assocs) + { + if (ActionModel.ASSOC_SCHEDULED_ACTION.equals(assoc.getTypeQName())) + { + actionAssoc = assoc; + break; + } + } + + return actionAssoc; + } + /** * Takes an entry out of the scheduler, if it's currently there. */ @@ -287,15 +430,36 @@ public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedAc public static class ScheduledPersistedActionServiceBootstrap extends AbstractLifecycleBean { private ScheduledPersistedActionServiceImpl service; + private RetryingTransactionHelper txnHelper; + public void setScheduledPersistedActionService(ScheduledPersistedActionServiceImpl scheduledPersistedActionService) { this.service = scheduledPersistedActionService; } + public void setTransactionHelper(RetryingTransactionHelper txnHelper) + { + this.txnHelper = txnHelper; + } + public void onBootstrap(ApplicationEvent event) { - service.locatePersistanceFolder(); - service.schedulePreviouslyPersisted(); + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + service.locatePersistanceFolder(); + service.schedulePreviouslyPersisted(); + return null; + } + }; + return txnHelper.doInTransaction(callback); + } + }, AuthenticationUtil.getSystemUserName()); } public void onShutdown(ApplicationEvent event) diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java index 9cfb6efa36..acfe2e84aa 100644 --- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java @@ -19,6 +19,7 @@ package org.alfresco.repo.action.scheduled; import java.util.Date; +import java.util.List; import javax.transaction.UserTransaction; @@ -28,8 +29,6 @@ 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; @@ -49,7 +48,6 @@ import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.SimpleTrigger; import org.quartz.Trigger; -import org.quartz.core.jmx.JobDetailSupport; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -58,320 +56,476 @@ 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 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 - protected void setUp() throws Exception - { - actionService = (ActionService) ctx.getBean("actionService"); - 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 - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - - UserTransaction txn = transactionService.getUserTransaction(); - txn.begin(); - - // Zap any existing persisted entries - scheduledRoot = ScheduledPersistedActionServiceImpl.SCHEDULED_ACTION_ROOT_NODE_REF; - for(ChildAssociationRef child : nodeService.getChildAssocs(scheduledRoot)) - { - QName type = nodeService.getType( child.getChildRef() ); - if(ScheduledPersistedActionServiceImpl.ACTION_TYPES.contains(type)) - { - nodeService.deleteNode(child.getChildRef()); - } - } - - // 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(); - } - - @Override - protected void tearDown() throws Exception { - UserTransaction txn = transactionService.getUserTransaction(); - txn.begin(); - - - txn.commit(); - } - - - /** - * Test that the {@link ScheduledPersistedAction} implementation - * behaves properly - */ - public void testPersistedActionImpl() throws Exception - { - // TODO - } - - /** - * Tests that the to-trigger stuff works properly - */ - - /** - * Tests that we can create, save, edit, delete etc the - * scheduled persisted actions - */ - - /** - * Tests that the listings work, both of all scheduled, - * and from an action - */ - - /** - * Tests that things get properly injected onto the job bean - */ - public void testJobBeanInjection() throws Exception - { - // 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); - - Scheduler scheduler = (Scheduler)ctx.getBean("schedulerFactory"); - scheduler.scheduleJob(details, now); + private ScheduledPersistedActionService service; + private ScheduledPersistedActionServiceImpl serviceImpl; + private Scheduler scheduler; - // Allow it to run - for(int i=0; i<20; i++) - { - if(! TestJob.ran) - Thread.sleep(50); - } - - // 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 - */ - public void testExecution() throws Exception - { - 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); + private TransactionService transactionService; + private RuntimeActionService runtimeActionService; + private ActionService actionService; + private NodeService nodeService; - - // ========================== - - - // 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 starts in 2 seconds time, and runs - // 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 - } - - // ============================================================================ + private Action testAction; + private Action testAction2; + + @Override + 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"); + + // Set the current security context as admin + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + + // Register the test executor, if needed + SleepActionExecuter.registerIfNeeded(ctx); + + // 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")); + + 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(); + } + + @Override + 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(); + } + + /** + * Test that the {@link ScheduledPersistedAction} implementation behaves + * properly + */ + public void testPersistedActionImpl() throws Exception + { + // TODO + } + + /** + * Tests that the to-trigger stuff works properly + */ + + /** + * 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); + service.saveSchedule(schedule); + + // 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()); + + // 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()); + } + + /** + * 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); + service.saveSchedule(schedule); + + // Load and check it hasn't changed + 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()); + + // Save and re-load without changes + service.saveSchedule(schedule); + 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()); + + // Make some small changes + retrieved.setScheduleIntervalCount(3); + service.saveSchedule(retrieved); + retrieved = serviceImpl.loadPersistentSchedule(((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef()); + 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); + service.saveSchedule(retrieved); + retrieved = serviceImpl.loadPersistentSchedule(((ScheduledPersistedActionImpl)schedule).getPersistedAtNodeRef()); + assertNotNull(retrieved); + assertEquals(testAction.getNodeRef(), retrieved.getAction().getNodeRef()); + assertEquals(now, retrieved.getScheduleStart()); + assertEquals(new Integer(3), retrieved.getScheduleIntervalCount()); + assertEquals(ScheduledPersistedAction.IntervalPeriod.Month, retrieved.getScheduleIntervalPeriod()); + + // TODO: associated action + } + + /** + * 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); + + retrieved = service.getSchedule(testAction); + assertNotNull(retrieved); + assertEquals(testAction.getNodeRef(), retrieved.getActionNodeRef()); + } + + /** + * 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()); + + // 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 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)); + + // 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)); + + // Delete the 2nd + service.deleteSchedule(schedule1); + assertEquals(0, service.listSchedules().size()); + assertNull(serviceImpl.loadPersistentSchedule(schedule1NodeRef)); + assertNull(serviceImpl.loadPersistentSchedule(schedule2NodeRef)); + + // Can add back in again after being deleted + service.saveSchedule(schedule1); + assertEquals(1, service.listSchedules().size()); + assertEquals(testAction.getNodeRef(), service.listSchedules().get(0).getActionNodeRef()); + } + + /** + * Tests that things get properly injected onto the job bean + */ + public void testJobBeanInjection() throws Exception + { + // 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); + + Scheduler scheduler = (Scheduler) ctx.getBean("schedulerFactory"); + scheduler.scheduleJob(details, now); + + // Allow it to run + for (int i = 0; i < 20; i++) + { + if (!TestJob.ran) + Thread.sleep(50); + } + + // 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 + */ + public void testExecution() throws Exception + { + 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()); + + 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("2Second", schedule.getScheduleInterval()); + + // Reset count + sleepActionExec.resetTimesExecuted(); + assertEquals(0, sleepActionExec.getTimesExecuted()); + + 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 starts in 2 seconds time, and runs + // 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("1Second", schedule.getScheduleInterval()); + + // Reset count + sleepActionExec.resetTimesExecuted(); + assertEquals(0, sleepActionExec.getTimesExecuted()); + + 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 + } + + // ============================================================================ + + /** + * 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); + } + } - - /** - * 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); - } - } - } 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 e03573ecb8..092d4a6bd2 100644 --- a/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java +++ b/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java @@ -83,26 +83,18 @@ public interface ScheduledPersistedAction /** - * Returns the interval in a form like 1D (1 day) - * or 2h (2 hours) + * Returns the interval in a form like 1Day (1 day) + * or 2Hour (2 hours) */ public String getScheduleInterval(); public static enum IntervalPeriod { - Month ('M'), - Week ('W'), - Day ('D'), - Hour ('h'), - Minute ('m'), - Second ('s'); - - private final char letter; - IntervalPeriod(char letter) { - this.letter = letter; - } - public char getLetter() { - return letter; - } + Month, + Week, + Day, + Hour, + Minute, + Second; } }