All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.quartz.impl.triggers.CalendarIntervalTriggerImpl Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version

/* 
 * Copyright 2001-2009 Terracotta, Inc. 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
 * use this file except in compliance with the License. You may obtain a copy 
 * of the License at 
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *   
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 * License for the specific language governing permissions and limitations 
 * under the License.
 * 
 */

package org.quartz.impl.triggers;

import java.util.Calendar;
import java.util.Date;

import org.quartz.CalendarIntervalScheduleBuilder;
import org.quartz.CalendarIntervalTrigger;
import org.quartz.CronTrigger;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.ScheduleBuilder;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerUtils;
import org.quartz.DateBuilder.IntervalUnit;


/**
 * 

A concrete {@link Trigger} that is used to fire a {@link org.quartz.JobDetail} * based upon repeating calendar time intervals.

* *

The trigger will fire every N (see {@link #setRepeatInterval(int)} ) units of calendar time * (see {@link #setRepeatIntervalUnit(IntervalUnit)}) as specified in the trigger's definition. * This trigger can achieve schedules that are not possible with {@link SimpleTrigger} (e.g * because months are not a fixed number of seconds) or {@link CronTrigger} (e.g. because * "every 5 months" is not an even divisor of 12).

* *

If you use an interval unit of MONTH then care should be taken when setting * a startTime value that is on a day near the end of the month. For example, * if you choose a start time that occurs on January 31st, and have a trigger with unit * MONTH and interval 1, then the next fire time will be February 28th, * and the next time after that will be March 28th - and essentially each subsequent firing will * occur on the 28th of the month, even if a 31st day exists. If you want a trigger that always * fires on the last day of the month - regardless of the number of days in the month, * you should use CronTrigger.

* * @see Trigger * @see CronTrigger * @see SimpleTrigger * @see NthIncludedDayTrigger * @see TriggerUtils * * @since 1.7 * * @author James House */ public class CalendarIntervalTriggerImpl extends AbstractTrigger implements CalendarIntervalTrigger, CoreTrigger { /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private static final long serialVersionUID = -2635982274232850343L; private static final int YEAR_TO_GIVEUP_SCHEDULING_AT = java.util.Calendar.getInstance().get(java.util.Calendar.YEAR) + 100; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Data members. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ private Date startTime = null; private Date endTime = null; private Date nextFireTime = null; private Date previousFireTime = null; private int repeatInterval = 0; private IntervalUnit repeatIntervalUnit = IntervalUnit.DAY; private int timesTriggered = 0; private boolean complete = false; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constructors. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Create a DateIntervalTrigger with no settings. *

*/ public CalendarIntervalTriggerImpl() { super(); } /** *

* Create a DateIntervalTrigger that will occur immediately, and * repeat at the the given interval. *

*/ public CalendarIntervalTriggerImpl(String name, IntervalUnit intervalUnit, int repeatInterval) { this(name, null, intervalUnit, repeatInterval); } /** *

* Create a DateIntervalTrigger that will occur immediately, and * repeat at the the given interval. *

*/ public CalendarIntervalTriggerImpl(String name, String group, IntervalUnit intervalUnit, int repeatInterval) { this(name, group, new Date(), null, intervalUnit, repeatInterval); } /** *

* Create a DateIntervalTrigger that will occur at the given time, * and repeat at the the given interval until the given end time. *

* * @param startTime * A Date set to the time for the Trigger * to fire. * @param endTime * A Date set to the time for the Trigger * to quit repeat firing. * @param intervalUnit * The repeat interval unit (minutes, days, months, etc). * @param repeatInterval * The number of milliseconds to pause between the repeat firing. */ public CalendarIntervalTriggerImpl(String name, Date startTime, Date endTime, IntervalUnit intervalUnit, int repeatInterval) { this(name, null, startTime, endTime, intervalUnit, repeatInterval); } /** *

* Create a DateIntervalTrigger that will occur at the given time, * and repeat at the the given interval until the given end time. *

* * @param startTime * A Date set to the time for the Trigger * to fire. * @param endTime * A Date set to the time for the Trigger * to quit repeat firing. * @param intervalUnit * The repeat interval unit (minutes, days, months, etc). * @param repeatInterval * The number of milliseconds to pause between the repeat firing. */ public CalendarIntervalTriggerImpl(String name, String group, Date startTime, Date endTime, IntervalUnit intervalUnit, int repeatInterval) { super(name, group); setStartTime(startTime); setEndTime(endTime); setRepeatIntervalUnit(intervalUnit); setRepeatInterval(repeatInterval); } /** *

* Create a DateIntervalTrigger that will occur at the given time, * fire the identified Job and repeat at the the given * interval until the given end time. *

* * @param startTime * A Date set to the time for the Trigger * to fire. * @param endTime * A Date set to the time for the Trigger * to quit repeat firing. * @param intervalUnit * The repeat interval unit (minutes, days, months, etc). * @param repeatInterval * The number of milliseconds to pause between the repeat firing. */ public CalendarIntervalTriggerImpl(String name, String group, String jobName, String jobGroup, Date startTime, Date endTime, IntervalUnit intervalUnit, int repeatInterval) { super(name, group, jobName, jobGroup); setStartTime(startTime); setEndTime(endTime); setRepeatIntervalUnit(intervalUnit); setRepeatInterval(repeatInterval); } /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** *

* Get the time at which the DateIntervalTrigger should occur. *

*/ @Override public Date getStartTime() { if(startTime == null) startTime = new Date(); return startTime; } /** *

* Set the time at which the DateIntervalTrigger should occur. *

* * @exception IllegalArgumentException * if startTime is null. */ @Override public void setStartTime(Date startTime) { if (startTime == null) { throw new IllegalArgumentException("Start time cannot be null"); } Date eTime = getEndTime(); if (eTime != null && startTime != null && eTime.before(startTime)) { throw new IllegalArgumentException( "End time cannot be before start time"); } this.startTime = startTime; } /** *

* Get the time at which the DateIntervalTrigger should quit * repeating. *

* * @see #getFinalFireTime() */ @Override public Date getEndTime() { return endTime; } /** *

* Set the time at which the DateIntervalTrigger should quit * repeating (and be automatically deleted). *

* * @exception IllegalArgumentException * if endTime is before start time. */ @Override public void setEndTime(Date endTime) { Date sTime = getStartTime(); if (sTime != null && endTime != null && sTime.after(endTime)) { throw new IllegalArgumentException( "End time cannot be before start time"); } this.endTime = endTime; } /* (non-Javadoc) * @see org.quartz.DateIntervalTriggerI#getRepeatIntervalUnit() */ public IntervalUnit getRepeatIntervalUnit() { return repeatIntervalUnit; } /** *

Set the interval unit - the time unit on with the interval applies.

*/ public void setRepeatIntervalUnit(IntervalUnit intervalUnit) { this.repeatIntervalUnit = intervalUnit; } /* (non-Javadoc) * @see org.quartz.DateIntervalTriggerI#getRepeatInterval() */ public int getRepeatInterval() { return repeatInterval; } /** *

* set the the time interval that will be added to the DateIntervalTrigger's * fire time (in the set repeat interval unit) in order to calculate the time of the * next trigger repeat. *

* * @exception IllegalArgumentException * if repeatInterval is < 1 */ public void setRepeatInterval( int repeatInterval) { if (repeatInterval < 0) { throw new IllegalArgumentException( "Repeat interval must be >= 1"); } this.repeatInterval = repeatInterval; } /* (non-Javadoc) * @see org.quartz.DateIntervalTriggerI#getTimesTriggered() */ public int getTimesTriggered() { return timesTriggered; } /** *

* Set the number of times the DateIntervalTrigger has already * fired. *

*/ public void setTimesTriggered(int timesTriggered) { this.timesTriggered = timesTriggered; } @Override protected boolean validateMisfireInstruction(int misfireInstruction) { if (misfireInstruction < MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) { return false; } if (misfireInstruction > MISFIRE_INSTRUCTION_DO_NOTHING) { return false; } return true; } /** *

* Updates the DateIntervalTrigger's state based on the * MISFIRE_INSTRUCTION_XXX that was selected when the DateIntervalTrigger * was created. *

* *

* If the misfire instruction is set to MISFIRE_INSTRUCTION_SMART_POLICY, * then the following scheme will be used:
*

    *
  • The instruction will be interpreted as MISFIRE_INSTRUCTION_FIRE_ONCE_NOW *
*

*/ @Override public void updateAfterMisfire(org.quartz.Calendar cal) { int instr = getMisfireInstruction(); if(instr == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) return; if (instr == MISFIRE_INSTRUCTION_SMART_POLICY) { instr = MISFIRE_INSTRUCTION_FIRE_ONCE_NOW; } if (instr == MISFIRE_INSTRUCTION_DO_NOTHING) { Date newFireTime = getFireTimeAfter(new Date()); while (newFireTime != null && cal != null && !cal.isTimeIncluded(newFireTime.getTime())) { newFireTime = getFireTimeAfter(newFireTime); } setNextFireTime(newFireTime); } else if (instr == MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) { // fire once now... setNextFireTime(new Date()); // the new fire time afterward will magically preserve the original // time of day for firing for day/week/month interval triggers, // because of the way getFireTimeAfter() works - in its always restarting // computation from the start time. } } /** *

* Called when the {@link Scheduler} has decided to 'fire' * the trigger (execute the associated Job), in order to * give the Trigger a chance to update itself for its next * triggering (if any). *

* * @see #executionComplete(JobExecutionContext, JobExecutionException) */ @Override public void triggered(org.quartz.Calendar calendar) { timesTriggered++; previousFireTime = nextFireTime; nextFireTime = getFireTimeAfter(nextFireTime); 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; } } } /** * * @see org.quartz.Trigger#updateWithNewCalendar(org.quartz.Calendar, long) */ @Override public void updateWithNewCalendar(org.quartz.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); } } } } /** *

* Called by the scheduler at the time a Trigger is first * added to the scheduler, in order to have the Trigger * compute its first fire time, based on any associated calendar. *

* *

* After this method has been called, getNextFireTime() * should return a valid answer. *

* * @return the first time at which the Trigger will be fired * by the scheduler, which is also the same value getNextFireTime() * will return (until after the first firing of the Trigger). *

*/ @Override public Date computeFirstFireTime(org.quartz.Calendar calendar) { nextFireTime = getStartTime(); 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) { return null; } } return nextFireTime; } /** *

* Returns the next time at which the Trigger is scheduled to fire. If * the trigger will not fire again, null will be returned. Note that * the time returned can possibly be in the past, if the time that was computed * for the trigger to next fire has already arrived, but the scheduler has not yet * been able to fire the trigger (which would likely be due to lack of resources * e.g. threads). *

* *

The value returned is not guaranteed to be valid until after the Trigger * has been added to the scheduler. *

*/ @Override public Date getNextFireTime() { return nextFireTime; } /** *

* Returns the previous time at which the DateIntervalTrigger * fired. If the trigger has not yet fired, null will be * returned. */ @Override public Date getPreviousFireTime() { return previousFireTime; } /** *

* Set the next time at which the DateIntervalTrigger should fire. *

* *

* This method should not be invoked by client code. *

*/ public void setNextFireTime(Date nextFireTime) { this.nextFireTime = nextFireTime; } /** *

* Set the previous time at which the DateIntervalTrigger fired. *

* *

* This method should not be invoked by client code. *

*/ public void setPreviousFireTime(Date previousFireTime) { this.previousFireTime = previousFireTime; } /** *

* Returns the next time at which the DateIntervalTrigger will * fire, after the given time. If the trigger will not fire after the given * time, null will be returned. *

*/ @Override public Date getFireTimeAfter(Date afterTime) { return getFireTimeAfter(afterTime, false); } protected Date getFireTimeAfter(Date afterTime, boolean ignoreEndTime) { if (complete) { return null; } // increment afterTme by a second, so that we are // comparing against a time after it! if (afterTime == null) { afterTime = new Date(System.currentTimeMillis() + 1000L); } else { afterTime = new Date(afterTime.getTime() + 1000L); } long startMillis = getStartTime().getTime(); long afterMillis = afterTime.getTime(); long endMillis = (getEndTime() == null) ? Long.MAX_VALUE : getEndTime() .getTime(); if (!ignoreEndTime && (endMillis <= afterMillis)) { return null; } if (afterMillis < startMillis) { return new Date(startMillis); } long secondsAfterStart = (afterMillis - startMillis) / 1000L; Date time = null; long repeatLong = getRepeatInterval(); Calendar aTime = Calendar.getInstance(); aTime.setTime(afterTime); Calendar sTime = Calendar.getInstance(); sTime.setTime(getStartTime()); sTime.setLenient(true); if(getRepeatIntervalUnit().equals(IntervalUnit.SECOND)) { long jumpCount = secondsAfterStart / repeatLong; if(secondsAfterStart % repeatLong != 0) jumpCount++; sTime.add(Calendar.SECOND, getRepeatInterval() * (int)jumpCount); time = sTime.getTime(); } else if(getRepeatIntervalUnit().equals(IntervalUnit.MINUTE)) { long jumpCount = secondsAfterStart / (repeatLong * 60L); if(secondsAfterStart % (repeatLong * 60L) != 0) jumpCount++; sTime.add(Calendar.MINUTE, getRepeatInterval() * (int)jumpCount); time = sTime.getTime(); } else if(getRepeatIntervalUnit().equals(IntervalUnit.HOUR)) { long jumpCount = secondsAfterStart / (repeatLong * 60L * 60L); if(secondsAfterStart % (repeatLong * 60L * 60L) != 0) jumpCount++; sTime.add(Calendar.HOUR_OF_DAY, getRepeatInterval() * (int)jumpCount); time = sTime.getTime(); } else if(getRepeatIntervalUnit().equals(IntervalUnit.DAY)) { sTime.setLenient(true); // Because intervals greater than an hour have an non-fixed number // of seconds in them (due to daylight savings, variation number of // days in each month, leap year, etc. ) we can't jump forward an // exact number of seconds to calculate the fire time as we can // with the second, minute and hour intervals. But, rather // than slowly crawling our way there by iteratively adding the // increment to the start time until we reach the "after time", // we can first make a big leap most of the way there... long jumpCount = secondsAfterStart / (repeatLong * 24L * 60L * 60L); // if we need to make a big jump, jump most of the way there, // but not all the way because in some cases we may over-shoot or under-shoot if(jumpCount > 20) { if(jumpCount < 50) jumpCount = (long) (jumpCount * 0.80); else if(jumpCount < 500) jumpCount = (long) (jumpCount * 0.90); else jumpCount = (long) (jumpCount * 0.95); sTime.add(java.util.Calendar.DAY_OF_YEAR, (int) (getRepeatInterval() * jumpCount)); } // now baby-step the rest of the way there... while(sTime.getTime().before(afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.DAY_OF_YEAR, getRepeatInterval()); } time = sTime.getTime(); } else if(getRepeatIntervalUnit().equals(IntervalUnit.WEEK)) { sTime.setLenient(true); // Because intervals greater than an hour have an non-fixed number // of seconds in them (due to daylight savings, variation number of // days in each month, leap year, etc. ) we can't jump forward an // exact number of seconds to calculate the fire time as we can // with the second, minute and hour intervals. But, rather // than slowly crawling our way there by iteratively adding the // increment to the start time until we reach the "after time", // we can first make a big leap most of the way there... long jumpCount = secondsAfterStart / (repeatLong * 7L * 24L * 60L * 60L); // if we need to make a big jump, jump most of the way there, // but not all the way because in some cases we may over-shoot or under-shoot if(jumpCount > 20) { if(jumpCount < 50) jumpCount = (long) (jumpCount * 0.80); else if(jumpCount < 500) jumpCount = (long) (jumpCount * 0.90); else jumpCount = (long) (jumpCount * 0.95); sTime.add(java.util.Calendar.WEEK_OF_YEAR, (int) (getRepeatInterval() * jumpCount)); } while(sTime.getTime().before(afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.WEEK_OF_YEAR, getRepeatInterval()); } time = sTime.getTime(); } else if(getRepeatIntervalUnit().equals(IntervalUnit.MONTH)) { sTime.setLenient(true); // because of the large variation in size of months, and // because months are already large blocks of time, we will // just advance via brute-force iteration. while(sTime.getTime().before(afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.MONTH, getRepeatInterval()); } time = sTime.getTime(); } else if(getRepeatIntervalUnit().equals(IntervalUnit.YEAR)) { while(sTime.getTime().before(afterTime) && (sTime.get(java.util.Calendar.YEAR) < YEAR_TO_GIVEUP_SCHEDULING_AT)) { sTime.add(java.util.Calendar.YEAR, getRepeatInterval()); } time = sTime.getTime(); } if (!ignoreEndTime && (endMillis <= time.getTime())) { return null; } return time; } /** *

* Returns the final time at which the DateIntervalTrigger will * fire, if there is no end time set, null will be returned. *

* *

* Note that the return time may be in the past. *

*/ @Override public Date getFinalFireTime() { if (complete || getEndTime() == null) { return null; } // back up a second from end time Date fTime = new Date(getEndTime().getTime() - 1000L); // find the next fire time after that fTime = getFireTimeAfter(fTime, true); // the the trigger fires at the end time, that's it! if(fTime.equals(getEndTime())) return fTime; // otherwise we have to back up one interval from the fire time after the end time Calendar lTime = Calendar.getInstance(); lTime.setTime(fTime); lTime.setLenient(true); if(getRepeatIntervalUnit().equals(IntervalUnit.SECOND)) { lTime.add(java.util.Calendar.SECOND, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.MINUTE)) { lTime.add(java.util.Calendar.MINUTE, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.HOUR)) { lTime.add(java.util.Calendar.HOUR_OF_DAY, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.DAY)) { lTime.add(java.util.Calendar.DAY_OF_YEAR, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.WEEK)) { lTime.add(java.util.Calendar.WEEK_OF_YEAR, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.MONTH)) { lTime.add(java.util.Calendar.MONTH, -1 * getRepeatInterval()); } else if(getRepeatIntervalUnit().equals(IntervalUnit.YEAR)) { lTime.add(java.util.Calendar.YEAR, -1 * getRepeatInterval()); } return lTime.getTime(); } /** *

* Determines whether or not the DateIntervalTrigger will occur * again. *

*/ @Override public boolean mayFireAgain() { return (getNextFireTime() != null); } /** *

* Validates whether the properties of the JobDetail are * valid for submission into a Scheduler. * * @throws IllegalStateException * if a required property (such as Name, Group, Class) is not * set. */ @Override public void validate() throws SchedulerException { super.validate(); if (repeatInterval < 1) { throw new SchedulerException("Repeat Interval cannot be zero."); } } /** * Get a {@link ScheduleBuilder} that is configured to produce a * schedule identical to this trigger's schedule. * * @see #getTriggerBuilder() */ @Override public ScheduleBuilder getScheduleBuilder() { CalendarIntervalScheduleBuilder cb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule() .withInterval(getRepeatInterval(), getRepeatIntervalUnit()); switch(getMisfireInstruction()) { case MISFIRE_INSTRUCTION_DO_NOTHING : cb.withMisfireHandlingInstructionDoNothing(); break; case MISFIRE_INSTRUCTION_FIRE_ONCE_NOW : cb.withMisfireHandlingInstructionFireAndProceed(); break; } return cb; } public boolean hasAdditionalProperties() { return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy