/* * Weblounge: Web Content Management System * Copyright (c) 2003 - 2011 The Weblounge Team * http://entwinemedia.com/weblounge * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package ch.entwine.weblounge.common.impl.scheduler; import ch.entwine.weblounge.common.scheduler.JobTrigger; import org.quartz.Calendar; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Trigger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * The <code>QuartzJobTrigger</code> wraps the Weblounge <code>JobTrigger</code> * to be used by the quartz scheduler. */ public final class QuartzJobTrigger extends Trigger { /** Serial version uid */ private static final long serialVersionUID = 3873501563763335486L; /** * <p> * Instructs the <code>{@link org.quartz.Scheduler}</code> that upon a * mis-fire situation, the <code>{@link QuartzJobTrigger}</code> wants to be * fired now by <code>Scheduler</code>. * </p> * * <p> * <i>NOTE:</i> This instruction should typically only be used for 'one-shot' * (non-repeating) Triggers. If it is used on a trigger with a repeat count > * 0 then it is equivalent to the instruction * <code>{@link #MISFIRE_INSTRUCTION_RESCHEDULE} * </code>. * </p> */ public static final int MISFIRE_INSTRUCTION_FIRE_NOW = 1; /** * <p> * Instructs the <code>{@link org.quartz.Scheduler}</code> that upon a * mis-fire situation, the <code>{@link QuartzJobTrigger}</code> wants to be * re-scheduled. This does obey the <code>Trigger</code> end-time however, so * if the next fire time is after the end-time the <code>Trigger</code> will * not fire again. * </p> * * <p> * <i>NOTE:</i> This instruction could cause the <code>Trigger</code> to go to * the 'COMPLETE' state after rescheduling, if all the repeat-fire-times where * missed. * </p> */ public static final int MISFIRE_INSTRUCTION_RESCHEDULE = 2; /** Do not schedule past this year (used to prevent endless loops) */ private static final int YEAR_TO_GIVEUP_SCHEDULING_AT = 2299; /** The logger */ private static final Logger logger = LoggerFactory.getLogger(QuartzJobTrigger.class); /** Weblounge job trigger */ private JobTrigger trigger = null; /** True if the trigger may fire multiple times */ private boolean mayFireAgain = true; /** Start date */ private Date startTime = null; /** End date */ private Date endTime = null; /** Time when the trigger should be fired next */ private Date nextFireTime = null; /** Time when the trigger was last fired */ private Date previousFireTime = null; /** Number of times that this trigger has been fired */ private int timesTriggered = 0; /** * Creates a new trigger that wraps the weblounge job trigger to be used by * the quartz scheduler. * * @param name * the job name * @param group * the quartz scheduler group * @param trigger * the weblounge job trigger */ public QuartzJobTrigger(String name, String group, JobTrigger trigger) { super(name, group); if (trigger == null) throw new IllegalArgumentException("Job trigger cannot be null"); this.trigger = trigger; // Initialize the settings Date now = new Date(); startTime = trigger.getNextExecutionAfter(now); mayFireAgain = startTime != null || trigger.getNextExecutionAfter(startTime) != null; } /** * The Quartz scheduler is creating clones of the triggers, so we need to * implement support for cloning, too. * * @see org.quartz.Trigger#clone() */ @Override public Object clone() { QuartzJobTrigger copy = null; try { copy = (QuartzJobTrigger) super.clone(); copy.trigger = (JobTrigger) trigger.clone(); } catch (CloneNotSupportedException ex) { throw new IncompatibleClassChangeError("Not Cloneable."); } return copy; } /** * Returns the weblounge trigger. * * @return the trigger */ JobTrigger getOriginalTrigger() { return trigger; } /** * Sets the next fire time. * * @param date * the date */ public void setNextFireTime(Date date) { this.nextFireTime = date; } /** * {@inheritDoc} * * @see org.quartz.QuartzJobTrigger#getNextFireTime() */ @Override public Date getNextFireTime() { return nextFireTime; } /** * {@inheritDoc} * * @see org.quartz.Trigger#computeFirstFireTime(org.quartz.Calendar) */ @Override public Date computeFirstFireTime(Calendar calendar) { Date firstFireTime = getStartTime(); while (firstFireTime != null && calendar != null && !calendar.isTimeIncluded(firstFireTime.getTime())) { firstFireTime = getFireTimeAfter(firstFireTime); if (firstFireTime == null) break; // avoid infinite loop java.util.Calendar c = java.util.Calendar.getInstance(); c.setTime(firstFireTime); if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) { return null; } } nextFireTime = (mayFireAgain) ? firstFireTime : null; return firstFireTime; } /** * {@inheritDoc} * * @see org.quartz.Trigger#executionComplete(org.quartz.JobExecutionContext, * org.quartz.JobExecutionException) */ @Override public int executionComplete(JobExecutionContext context, JobExecutionException result) { if (result != null && result.refireImmediately()) { return INSTRUCTION_RE_EXECUTE_JOB; } if (result != null && result.unscheduleFiringTrigger()) { return INSTRUCTION_SET_TRIGGER_COMPLETE; } if (result != null && result.unscheduleAllTriggers()) { return INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE; } if (!mayFireAgain()) { return INSTRUCTION_DELETE_TRIGGER; } return INSTRUCTION_NOOP; } /** * {@inheritDoc} * * @see org.quartz.Trigger#getEndTime() */ @Override public Date getEndTime() { return endTime; } /** * {@inheritDoc} * * @see org.quartz.Trigger#getFinalFireTime() */ @Override public Date getFinalFireTime() { return endTime; } /** * {@inheritDoc} * * @see org.quartz.Trigger#getFireTimeAfter(java.util.Date) */ @Override public Date getFireTimeAfter(Date date) { try { return trigger.getNextExecutionAfter(date); } catch (Throwable t) { logger.error("Job implementation threw exception when asked for next trigger date", t); return null; } } /** * {@inheritDoc} * * @see org.quartz.Trigger#getPreviousFireTime() */ @Override public Date getPreviousFireTime() { return previousFireTime; } /** * {@inheritDoc} * * @see org.quartz.Trigger#getStartTime() */ @Override public Date getStartTime() { return startTime; } /** * {@inheritDoc} * * @see org.quartz.Trigger#mayFireAgain() */ @Override public boolean mayFireAgain() { return mayFireAgain && getNextFireTime() != null; } /** * {@inheritDoc} * * @see org.quartz.Trigger#setEndTime(java.util.Date) */ @Override public void setEndTime(Date endTime) { this.endTime = endTime; } /** * {@inheritDoc} * * @see org.quartz.Trigger#setStartTime(java.util.Date) */ @Override public void setStartTime(Date startTime) { this.startTime = startTime; } /** * {@inheritDoc} * * @see org.quartz.Trigger#triggered(org.quartz.Calendar) */ @Override public void triggered(Calendar calendar) { try { timesTriggered++; previousFireTime = nextFireTime; trigger.triggered(previousFireTime); } catch (Throwable t) { logger.error("Job implementation threw exception on trigger callback", t); } finally { nextFireTime = getFireTimeAfter(nextFireTime); } // Make sure the next fire time is not explicitly excluded while (nextFireTime != null && calendar != null && !calendar.isTimeIncluded(nextFireTime.getTime())) { nextFireTime = getFireTimeAfter(nextFireTime); if (nextFireTime == null) break; // avoid infinite loop java.util.Calendar c = java.util.Calendar.getInstance(); c.setTime(nextFireTime); if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) { nextFireTime = null; } } } /** * {@inheritDoc} * * @see org.quartz.Trigger#updateAfterMisfire(org.quartz.Calendar) */ @Override public void updateAfterMisfire(Calendar calendar) { int instr = getMisfireInstruction(); if (instr == Trigger.MISFIRE_INSTRUCTION_SMART_POLICY) { if (!mayFireAgain) { instr = MISFIRE_INSTRUCTION_FIRE_NOW; } else { instr = MISFIRE_INSTRUCTION_RESCHEDULE; } } else if (instr == MISFIRE_INSTRUCTION_FIRE_NOW && mayFireAgain) { instr = MISFIRE_INSTRUCTION_RESCHEDULE; } // Reschedule? if (instr == MISFIRE_INSTRUCTION_FIRE_NOW) { setNextFireTime(new Date()); } else if (instr == MISFIRE_INSTRUCTION_RESCHEDULE) { Date newFireTime = getFireTimeAfter(new Date()); while (newFireTime != null && calendar != null && !calendar.isTimeIncluded(newFireTime.getTime())) { newFireTime = getFireTimeAfter(newFireTime); if (newFireTime == null) break; // avoid infinite loop java.util.Calendar c = java.util.Calendar.getInstance(); c.setTime(newFireTime); if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) { newFireTime = null; } } setNextFireTime(newFireTime); } } /** * {@inheritDoc} * * @see org.quartz.Trigger#updateWithNewCalendar(org.quartz.Calendar, long) */ @Override public void updateWithNewCalendar(Calendar calendar, long misfireThreshold) { nextFireTime = getFireTimeAfter(previousFireTime); if (nextFireTime == null || calendar == null) { return; } Date now = new Date(); while (nextFireTime != null && !calendar.isTimeIncluded(nextFireTime.getTime())) { nextFireTime = getFireTimeAfter(nextFireTime); if (nextFireTime == null) break; // avoid infinite loop java.util.Calendar c = java.util.Calendar.getInstance(); c.setTime(nextFireTime); if (c.get(java.util.Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) { nextFireTime = null; } if (nextFireTime != null && nextFireTime.before(now)) { long diff = now.getTime() - nextFireTime.getTime(); if (diff >= misfireThreshold) { nextFireTime = getFireTimeAfter(nextFireTime); } } } } /** * {@inheritDoc} * * @see org.quartz.Trigger#validateMisfireInstruction(int) */ @Override protected boolean validateMisfireInstruction(int misfireInstruction) { if (misfireInstruction < MISFIRE_INSTRUCTION_SMART_POLICY) { return false; } else if (misfireInstruction > MISFIRE_INSTRUCTION_RESCHEDULE) { return false; } return true; } /** * Get the number of times the <code>QuartzJobTrigger</code> has already * fired. * * @return the number of times that this trigger has been fired */ public int getTimesTriggered() { return timesTriggered; } }