ALF-4346 - Start on the scheduled persisted actions service implementation

Tests are only stubbed out for now


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@21813 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Nick Burch
2010-08-16 16:37:41 +00:00
parent 4f25d66103
commit 103c17d160
6 changed files with 441 additions and 16 deletions

View File

@@ -483,4 +483,17 @@
</bean> </bean>
<!-- Supports scheduled, persisted actions -->
<bean id="scheduledPersistedActionService" class="org.alfresco.repo.action.scheduled.ScheduledPersistedActionServiceImpl" init-method="schedulePreviouslyPersisted">
<property name="nodeService" ref="NodeService" />
<property name="startupNodeService" ref="nodeService" />
<property name="startupSearchService" ref="searchService"/>
<property name="actionService" ref="ActionService"/>
<property name="namespaceService" ref="namespaceService"/>
<property name="runtimeActionService" ref="actionService"/>
<property name="scheduler">
<ref bean="schedulerFactory" />
</property>
</bean>
</beans> </beans>

View File

@@ -22,6 +22,9 @@ import java.util.Date;
import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction; 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 * The scheduling wrapper around a persisted
@@ -33,17 +36,26 @@ import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction;
*/ */
public class ScheduledPersistedActionImpl implements ScheduledPersistedAction public class ScheduledPersistedActionImpl implements ScheduledPersistedAction
{ {
private NodeRef persistedAtNodeRef;
private Action action; private Action action;
private Date scheduleStart; private Date scheduleStart;
private Integer intervalCount; private Integer intervalCount;
private IntervalPeriod intervalPeriod; private IntervalPeriod intervalPeriod;
public ScheduledPersistedActionImpl(Action action) protected ScheduledPersistedActionImpl(Action action)
{ {
this.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 */ /** Get the action which the schedule applies to */
public Action getAction() public Action getAction()
{ {
@@ -123,4 +135,72 @@ public class ScheduledPersistedActionImpl implements ScheduledPersistedAction
} }
return intervalCount.toString() + intervalPeriod.getLetter(); 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;
}
} }

View File

@@ -18,11 +18,31 @@
*/ */
package org.alfresco.repo.action.scheduled; 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.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.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction; import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedAction;
import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedActionService; 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 * A service which handles the scheduling of the
@@ -34,33 +54,204 @@ import org.alfresco.service.cmr.action.scheduled.ScheduledPersistedActionService
* @author Nick Burch * @author Nick Burch
* @since 3.4 * @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<QName> ACTION_TYPES = new HashSet<QName>(
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<NodeRef> 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<ScheduledPersistedAction> actions = listSchedules(startupNodeService);
for(ScheduledPersistedAction action : actions)
{
addToScheduler((ScheduledPersistedActionImpl)action);
}
}
/** /**
* Creates a new schedule, for the specified 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, * Saves the changes to the schedule to the repository,
* and updates the Scheduler with any changed details. * 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 * Removes the schedule for the action, and cancels future
* executions of it. * executions of it.
* The persisted action is unchanged. * 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 * Returns the schedule for the specified action, or
* null if it isn't currently scheduled. * 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. * Returns all currently scheduled actions.
*/ */
public List<ScheduledPersistedAction> listSchedules(); public List<ScheduledPersistedAction> listSchedules()
{
return listSchedules(nodeService);
}
private List<ScheduledPersistedAction> listSchedules(NodeService nodeService)
{
List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(SCHEDULED_ACTION_ROOT_NODE_REF, ACTION_TYPES);
List<ScheduledPersistedAction> scheduledActions = new ArrayList<ScheduledPersistedAction>(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;
}
} }

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}

View File

@@ -86,14 +86,14 @@ public class ReplicationDefinitionPersisterImpl implements ReplicationDefinition
List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(REPLICATION_ACTION_ROOT_NODE_REF, ACTION_TYPES); List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(REPLICATION_ACTION_ROOT_NODE_REF, ACTION_TYPES);
List<ReplicationDefinition> renderingActions = new ArrayList<ReplicationDefinition>(childAssocs.size()); List<ReplicationDefinition> replicationActions = new ArrayList<ReplicationDefinition>(childAssocs.size());
for (ChildAssociationRef actionAssoc : childAssocs) for (ChildAssociationRef actionAssoc : childAssocs)
{ {
Action nextAction = runtimeActionService.createAction(actionAssoc.getChildRef()); Action nextAction = runtimeActionService.createAction(actionAssoc.getChildRef());
renderingActions.add(new ReplicationDefinitionImpl(nextAction)); replicationActions.add(new ReplicationDefinitionImpl(nextAction));
} }
return renderingActions; return replicationActions;
} }
public List<ReplicationDefinition> loadReplicationDefinitions(String targetName) public List<ReplicationDefinition> loadReplicationDefinitions(String targetName)

View File

@@ -86,18 +86,28 @@ public interface ScheduledPersistedAction
public static enum IntervalPeriod { public static enum IntervalPeriod {
Month ('M'), Month ('M', -1),
Week ('W'), Week ('W', 7*24*60*60*1000),
Day ('D'), Day ('D', 24*60*60*1000),
Hour ('h'), Hour ('h', 60*60*1000),
Minute ('m'); Minute ('m', 60*1000);
private final char letter; private final char letter;
IntervalPeriod(char letter) { private final long interval;
IntervalPeriod(char letter, long interval) {
this.letter = letter; this.letter = letter;
this.interval = interval;
} }
public char getLetter() { public char getLetter() {
return letter; return letter;
} }
/**
* Returns the interval of one of these
* periods, in milliseconds
*/
public long getInterval() {
return interval;
}
} }
} }