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
This commit is contained in:
Nick Burch
2010-09-01 16:16:32 +00:00
parent 73ae4f71ca
commit 1f5c3a1d20
3 changed files with 1298 additions and 841 deletions

View File

@@ -205,36 +205,116 @@ public class ScheduledPersistedActionImpl implements ScheduledPersistedAction
return null; 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 they don't want it to repeat, just use a simple interval
if(getScheduleInterval() == null) if(getScheduleInterval() == null)
{ {
SimpleTrigger trigger = new SimpleTrigger( SimpleTrigger trigger = new SimpleTrigger(
triggerName, null, triggerName, null,
scheduleStart startAt
); );
trigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); trigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW);
return trigger; return trigger;
} }
return buildDateIntervalTrigger(triggerName, startAt, endAt);
}
private DateIntervalTrigger buildDateIntervalTrigger(String triggerName, Date startAt, Date endAt)
{
// There are some repeating rules // There are some repeating rules
// Create a Date Interval trigger // Create a Date Interval trigger
DateIntervalTrigger.IntervalUnit quartzInterval = DateIntervalTrigger.IntervalUnit quartzInterval =
DateIntervalTrigger.IntervalUnit.valueOf( DateIntervalTrigger.IntervalUnit.valueOf(
intervalPeriod.toString().toUpperCase() 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 // Create the interval
DateIntervalTrigger trigger = new DateIntervalTrigger( DateIntervalTrigger trigger = new DateIntervalTrigger(
triggerName, null, triggerName, null,
start, end, startAt, endAt,
quartzInterval, intervalCount quartzInterval, intervalCount
); );
trigger.setMisfireInstruction( DateIntervalTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW ); trigger.setMisfireInstruction( DateIntervalTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW );

View File

@@ -33,7 +33,9 @@ import org.alfresco.repo.action.RuntimeActionService;
import org.alfresco.repo.model.Repository; import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; 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.RetryingTransactionHelper;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
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.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) protected void addToScheduler(ScheduledPersistedActionImpl schedule)
{ {
// Wrap it up in Quartz bits // Wrap it up in Quartz bits
JobDetail details = buildJobDetail(schedule); final JobDetail details = buildJobDetail(schedule);
Trigger trigger = schedule.asTrigger(); final Trigger trigger = schedule.asTrigger();
// Schedule it // As soon as the transaction commits, add it
try AlfrescoTransactionSupport.bindListener(
{ new TransactionListenerAdapter() {
scheduler.scheduleJob(details, trigger); @Override
} public void afterCommit() {
catch (SchedulerException e) // Schedule it with Quartz
{ try
// Probably means scheduler is shutting down {
log.warn(e); scheduler.scheduleJob(details, trigger);
} }
catch (SchedulerException e)
{
// Probably means scheduler is shutting down
log.warn(e);
}
}
}
);
} }
protected JobDetail buildJobDetail(ScheduledPersistedActionImpl schedule) protected JobDetail buildJobDetail(ScheduledPersistedActionImpl schedule)