diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml
index a99fbf944a..9815a87549 100644
--- a/config/alfresco/scheduled-jobs-context.xml
+++ b/config/alfresco/scheduled-jobs-context.xml
@@ -483,4 +483,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java
index 99e5ad6d0b..690151fecd 100644
--- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java
+++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionImpl.java
@@ -22,6 +22,9 @@ import java.util.Date;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.quartz.SimpleTrigger;
+import org.quartz.Trigger;
/**
* The scheduling wrapper around a persisted
@@ -33,17 +36,26 @@ import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction;
*/
public class ScheduledPersistedActionImpl implements ScheduledPersistedAction
{
+ private NodeRef persistedAtNodeRef;
private Action action;
private Date scheduleStart;
private Integer intervalCount;
private IntervalPeriod intervalPeriod;
- public ScheduledPersistedActionImpl(Action action)
+ protected ScheduledPersistedActionImpl(Action action)
{
this.action = action;
}
+ /**
+ * Get the persisted nodeRef for this schedule
+ */
+ protected NodeRef getPersistedAtNodeRef()
+ {
+ return persistedAtNodeRef;
+ }
+
/** Get the action which the schedule applies to */
public Action getAction()
{
@@ -123,4 +135,72 @@ public class ScheduledPersistedActionImpl implements ScheduledPersistedAction
}
return intervalCount.toString() + intervalPeriod.getLetter();
}
+
+ /**
+ * Returns a Quartz trigger definition based on the current
+ * scheduling details.
+ * May only be called once this object has been persisted
+ */
+ public Trigger asTrigger()
+ {
+ if(persistedAtNodeRef == null)
+ throw new IllegalStateException("Must be persisted first");
+
+ // Use our nodeRef as the unique title
+ String triggerName = persistedAtNodeRef.toString();
+
+ // Monthly is a special case, since the period
+ // will vary
+ // TODO - Make more things use DateIntervalTrigger
+ if(intervalPeriod == IntervalPeriod.Month)
+ {
+// TODO
+// DateIntervalTrigger trigger = new DateIntervalTrigger(
+// triggerName, null,
+// scheduleStart, null,
+// DateIntervalTrigger.IntervalUnit.MONTH,
+// intervalCount
+// );
+// trigger.setMisfireInstruction( DateIntervalTrigger.MISFIRE_INSTRUCTION_FIRE_NOW );
+ }
+
+ SimpleTrigger trigger = null;
+
+ // Is it Start Date + Repeat Interval?
+ if(scheduleStart != null && getScheduleInterval() != null)
+ {
+ trigger = new SimpleTrigger(
+ triggerName, null,
+ scheduleStart, null,
+ SimpleTrigger.REPEAT_INDEFINITELY,
+ intervalCount * intervalPeriod.getInterval()
+ );
+ }
+
+ // Is it a single Start Date?
+ if(scheduleStart != null && getScheduleInterval() == null)
+ {
+ trigger = new SimpleTrigger(
+ triggerName, null,
+ scheduleStart
+ );
+ }
+
+ // Is it start now, run with Repeat Interval?
+ if(getScheduleInterval() != null)
+ {
+ trigger = new SimpleTrigger(
+ triggerName, null,
+ SimpleTrigger.REPEAT_INDEFINITELY,
+ intervalCount * intervalPeriod.getInterval()
+ );
+ }
+
+ if(trigger != null)
+ {
+ // If we miss running, run as soon after as we can
+ trigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW);
+ }
+ return trigger;
+ }
}
diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java
index 0c97b45f32..e4d00e8331 100644
--- a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java
+++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceImpl.java
@@ -18,11 +18,31 @@
*/
package org.alfresco.repo.action.scheduled;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import org.alfresco.repo.action.ActionModel;
+import org.alfresco.repo.action.RuntimeActionService;
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.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
/**
* A service which handles the scheduling of the
@@ -34,33 +54,204 @@ import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedActionService
* @author Nick Burch
* @since 3.4
*/
-public interface ScheduledPersistedActionServiceImpl extends ScheduledPersistedActionService {
+public class ScheduledPersistedActionServiceImpl implements ScheduledPersistedActionService {
+ protected static final String SCHEDULED_ACTION_ROOT_PATH =
+ "/app:company_home/app:dictionary/cm:Scheduled_x0020_Actions";
+ protected static NodeRef SCHEDULED_ACTION_ROOT_NODE_REF;
+ protected static final Set ACTION_TYPES = new HashSet(
+ Arrays.asList(new QName[] { ActionModel.TYPE_ACTION })); // TODO
+
+ protected static final String SCHEDULER_GROUP = "PersistedActions";
+
+ private static final Log log = LogFactory.getLog(ScheduledPersistedActionServiceImpl.class);
+
+ private Scheduler scheduler;
+ private NodeService nodeService;
+ private NodeService startupNodeService;
+ private ActionService actionService;
+ private SearchService startupSearchService;
+ private NamespaceService namespaceService;
+ private RuntimeActionService runtimeActionService;
+
+ public void setScheduler(Scheduler scheduler)
+ {
+ this.scheduler = scheduler;
+ }
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ /**
+ * Sets the node service to use during startup, which
+ * won't do permissions check etc
+ */
+ public void setStartupNodeService(NodeService startupNodeService)
+ {
+ this.startupNodeService = startupNodeService;
+ }
+
+ public void setStartupSearchService(SearchService startupSearchService)
+ {
+ this.startupSearchService = startupSearchService;
+ }
+
+ public void setActionService(ActionService actionService)
+ {
+ this.actionService = actionService;
+ }
+
+ public void setRuntimeActionService(RuntimeActionService runtimeActionService)
+ {
+ this.runtimeActionService = runtimeActionService;
+ }
+
+ public void setNamespaceService(NamespaceService namespaceService)
+ {
+ this.namespaceService = namespaceService;
+ }
+
+
+ /**
+ * Find all our previously persisted scheduled actions, and
+ * tell the scheduler to start handling them.
+ * Called by spring when startup is complete.
+ */
+ public void schedulePreviouslyPersisted() {
+ // Grab the path of our bit of the data dictionary
+ StoreRef spacesStore = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
+ List nodes = startupSearchService.selectNodes(
+ startupNodeService.getRootNode(spacesStore),
+ SCHEDULED_ACTION_ROOT_PATH,
+ null, namespaceService, false
+ );
+ if(nodes.size() != 1)
+ {
+ throw new IllegalStateException("Tries to find the Scheduled Actions Data Dictionary" +
+ " folder at " + SCHEDULED_ACTION_ROOT_PATH + " but got " + nodes.size() + "results");
+ }
+ SCHEDULED_ACTION_ROOT_NODE_REF = nodes.get(0);
+
+ // Now, look up our persisted actions and schedule
+ List actions = listSchedules(startupNodeService);
+ for(ScheduledPersistedAction action : actions)
+ {
+ addToScheduler((ScheduledPersistedActionImpl)action);
+ }
+ }
+
+
/**
* Creates a new schedule, for the specified Action.
*/
- public ScheduledPersistedAction createSchedule(Action persistedAction);
+ public ScheduledPersistedAction createSchedule(Action persistedAction)
+ {
+ return new ScheduledPersistedActionImpl(persistedAction);
+ }
/**
* Saves the changes to the schedule to the repository,
* and updates the Scheduler with any changed details.
*/
- public void saveSchedule(ScheduledPersistedAction schedule);
+ public void saveSchedule(ScheduledPersistedAction schedule)
+ {
+ removeFromScheduler((ScheduledPersistedActionImpl)schedule);
+ addToScheduler((ScheduledPersistedActionImpl)schedule);
+
+ // TODO
+ }
/**
* Removes the schedule for the action, and cancels future
* executions of it.
* The persisted action is unchanged.
*/
- public void deleteSchedule(ScheduledPersistedAction schedule);
+ public void deleteSchedule(ScheduledPersistedAction schedule)
+ {
+ removeFromScheduler((ScheduledPersistedActionImpl)schedule);
+
+ // TODO
+ }
/**
* Returns the schedule for the specified action, or
* null if it isn't currently scheduled.
*/
- public ScheduledPersistedAction getSchedule(Action persistedAction);
+ public ScheduledPersistedAction getSchedule(Action persistedAction)
+ {
+ // TODO
+ return null;
+ }
/**
* Returns all currently scheduled actions.
*/
- public List listSchedules();
+ public List listSchedules()
+ {
+ return listSchedules(nodeService);
+ }
+ private List listSchedules(NodeService nodeService)
+ {
+ List childAssocs = nodeService.getChildAssocs(SCHEDULED_ACTION_ROOT_NODE_REF, ACTION_TYPES);
+
+ List scheduledActions = new ArrayList(childAssocs.size());
+ for (ChildAssociationRef actionAssoc : childAssocs)
+ {
+ // TODO
+// Action nextAction = runtimeActionService.createAction(actionAssoc.getChildRef());
+// renderingActions.add(new ReplicationDefinitionImpl(nextAction));
+ }
+
+ return scheduledActions;
+ }
+
+
+ /**
+ * Takes an entry out of the scheduler, if it's currently
+ * there.
+ */
+ protected void removeFromScheduler(ScheduledPersistedActionImpl schedule)
+ {
+ // Jobs are indexed by the persisted node ref
+ try {
+ scheduler.deleteJob(
+ schedule.getPersistedAtNodeRef().toString(),
+ SCHEDULER_GROUP
+ );
+ } catch (SchedulerException e) {
+ // Probably means scheduler is shutting down
+ log.warn(e);
+ }
+ }
+ /**
+ * Adds a new entry into the scheduler
+ */
+ protected void addToScheduler(ScheduledPersistedActionImpl schedule)
+ {
+ // Wrap it up in Quartz bits
+ JobDetail details = buildJobDetail(schedule);
+ Trigger trigger = schedule.asTrigger();
+
+ // Schedule it
+ try {
+ scheduler.scheduleJob(details, trigger);
+ } catch (SchedulerException e) {
+ // Probably means scheduler is shutting down
+ log.warn(e);
+ }
+ }
+
+ protected JobDetail buildJobDetail(ScheduledPersistedActionImpl schedule)
+ {
+ JobDetail detail = new JobDetail(
+ schedule.getPersistedAtNodeRef().toString(),
+ SCHEDULER_GROUP,
+ null // TODO
+ );
+
+ // TODO
+
+ return detail;
+ }
}
diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java
new file mode 100644
index 0000000000..622d0cdfba
--- /dev/null
+++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledPersistedActionServiceTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.action.scheduled;
+
+import javax.transaction.UserTransaction;
+
+import junit.framework.TestCase;
+
+import org.alfresco.repo.model.Repository;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+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.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.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * Unit tests for the {@link ScheduledPersistedActionService}
+ */
+public class ScheduledPersistedActionServiceTest extends TestCase
+{
+ private static ConfigurableApplicationContext ctx =
+ (ConfigurableApplicationContext)ApplicationContextHelper.getApplicationContext();
+
+ private TransactionService transactionService;
+ private ActionService actionService;
+ private NodeService nodeService;
+ private Repository repositoryHelper;
+
+ 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");
+
+
+ // 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());
+ }
+ }
+
+ // 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 actually get run correctly
+ */
+ public void testExecution() throws Exception
+ {
+ // A job due to start in 2 seconds
+ // TODO
+
+ // A job that runs every 2 seconds
+ // TODO
+
+ // A job that starts in 2 seconds time, and runs
+ // every second
+ // TODO
+ }
+}
diff --git a/source/java/org/alfresco/repo/replication/ReplicationDefinitionPersisterImpl.java b/source/java/org/alfresco/repo/replication/ReplicationDefinitionPersisterImpl.java
index 78310d1f2e..c4ebfb34ed 100644
--- a/source/java/org/alfresco/repo/replication/ReplicationDefinitionPersisterImpl.java
+++ b/source/java/org/alfresco/repo/replication/ReplicationDefinitionPersisterImpl.java
@@ -86,14 +86,14 @@ public class ReplicationDefinitionPersisterImpl implements ReplicationDefinition
List childAssocs = nodeService.getChildAssocs(REPLICATION_ACTION_ROOT_NODE_REF, ACTION_TYPES);
- List renderingActions = new ArrayList(childAssocs.size());
+ List replicationActions = new ArrayList(childAssocs.size());
for (ChildAssociationRef actionAssoc : childAssocs)
{
Action nextAction = runtimeActionService.createAction(actionAssoc.getChildRef());
- renderingActions.add(new ReplicationDefinitionImpl(nextAction));
+ replicationActions.add(new ReplicationDefinitionImpl(nextAction));
}
- return renderingActions;
+ return replicationActions;
}
public List loadReplicationDefinitions(String targetName)
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 b1b7949aad..ac70e1753c 100644
--- a/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java
+++ b/source/java/org/alfresco/service/cmr/action/scheduled/ScheduledPersistedAction.java
@@ -86,18 +86,28 @@ public interface ScheduledPersistedAction
public static enum IntervalPeriod {
- Month ('M'),
- Week ('W'),
- Day ('D'),
- Hour ('h'),
- Minute ('m');
+ Month ('M', -1),
+ Week ('W', 7*24*60*60*1000),
+ Day ('D', 24*60*60*1000),
+ Hour ('h', 60*60*1000),
+ Minute ('m', 60*1000);
private final char letter;
- IntervalPeriod(char letter) {
+ private final long interval;
+
+ IntervalPeriod(char letter, long interval) {
this.letter = letter;
+ this.interval = interval;
}
public char getLetter() {
return letter;
}
+ /**
+ * Returns the interval of one of these
+ * periods, in milliseconds
+ */
+ public long getInterval() {
+ return interval;
+ }
}
}