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

com.helger.quartz.impl.triggers.CronTrigger Maven / Gradle / Ivy

/**
 * All content copyright Terracotta, Inc., unless otherwise indicated. All rights reserved.
 *
 * Copyright (C) 2016-2020 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * 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 com.helger.quartz.impl.triggers;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.datetime.PDTFactory;
import com.helger.quartz.CQuartz;
import com.helger.quartz.CronExpression;
import com.helger.quartz.CronScheduleBuilder;
import com.helger.quartz.ICalendar;
import com.helger.quartz.ICronTrigger;
import com.helger.quartz.IScheduleBuilder;
import com.helger.quartz.QCloneUtils;

/**
 * A concrete {@link com.helger.quartz.ITrigger} that is used to
 * fire a {@link com.helger.quartz.IJobDetail} at given moments in
 * time, defined with Unix 'cron-like' definitions.
 *
 * @author Sharada Jambula, James House
 * @author Contributions from Mads Henderson
 */
public class CronTrigger extends AbstractTrigger  implements ICronTrigger
{
  protected static final int YEAR_TO_GIVEUP_SCHEDULING_AT = CQuartz.MAX_YEAR;

  private CronExpression m_aCronEx;
  private Date m_aStartTime;
  private Date m_aEndTime;
  private Date m_aNextFireTime;
  private Date m_aPreviousFireTime;
  private transient TimeZone m_aTimeZone;

  public CronTrigger (@Nonnull final CronTrigger aOther)
  {
    super (aOther);
    m_aCronEx = QCloneUtils.getClone (aOther.m_aCronEx);
    m_aStartTime = QCloneUtils.getClone (aOther.m_aStartTime);
    m_aEndTime = QCloneUtils.getClone (aOther.m_aEndTime);
    m_aNextFireTime = QCloneUtils.getClone (aOther.m_aNextFireTime);
    m_aPreviousFireTime = QCloneUtils.getClone (aOther.m_aPreviousFireTime);
    m_aTimeZone = QCloneUtils.getClone (aOther.m_aTimeZone);
  }

  /**
   * Create a CronTrigger with no settings.
* The start-time will also be set to the current time, and the time zone will * be set the the system's default time zone. */ public CronTrigger () { super (); setStartTime (new Date ()); setTimeZone (TimeZone.getDefault ()); } @Nullable public String getCronExpression () { return m_aCronEx == null ? null : m_aCronEx.getCronExpression (); } public void setCronExpression (@Nonnull final String cronExpression) throws ParseException { final TimeZone origTz = getTimeZone (); m_aCronEx = new CronExpression (cronExpression); m_aCronEx.setTimeZone (origTz); } /** * Set the CronExpression to the given one. The TimeZone on the passed-in * CronExpression over-rides any that was already set on the Trigger. */ public void setCronExpression (@Nonnull final CronExpression cronExpression) { m_aCronEx = cronExpression; m_aTimeZone = cronExpression.getTimeZone (); } @Nullable public final Date getStartTime () { return m_aStartTime; } public final void setStartTime (@Nonnull final Date startTime) { ValueEnforcer.notNull (startTime, "StartTime"); final Date eTime = getEndTime (); if (eTime != null && eTime.before (startTime)) { throw new IllegalArgumentException ("End time cannot be before start time"); } // round off millisecond... // Note timeZone is not needed here as parameter for // Calendar.getInstance(), // since time zone is implicit when using a Date in the setTime method. final Calendar cl = PDTFactory.createCalendar (); cl.setTime (startTime); cl.set (Calendar.MILLISECOND, 0); m_aStartTime = cl.getTime (); } @Nullable public final Date getEndTime () { return m_aEndTime; } public final void setEndTime (@Nullable final Date endTime) { final Date sTime = getStartTime (); if (sTime != null && endTime != null && sTime.after (endTime)) throw new IllegalArgumentException ("End time cannot be before start time"); m_aEndTime = endTime; } public Date getNextFireTime () { return m_aNextFireTime; } public void setNextFireTime (final Date nextFireTime) { m_aNextFireTime = nextFireTime; } public Date getPreviousFireTime () { return m_aPreviousFireTime; } public void setPreviousFireTime (final Date previousFireTime) { m_aPreviousFireTime = previousFireTime; } @Nonnull public TimeZone getTimeZone () { if (m_aCronEx != null) return m_aCronEx.getTimeZone (); if (m_aTimeZone == null) m_aTimeZone = TimeZone.getDefault (); return m_aTimeZone; } /** * Sets the time zone for which the cronExpression of this * CronTrigger will be resolved.
* If {@link #setCronExpression(CronExpression)} is called after this method, * the TimeZon setting on the CronExpression will "win". However if * {@link #setCronExpression(String)} is called after this method, the time * zone applied by this method will remain in effect, since the String cron * expression does not carry a time zone! */ public void setTimeZone (@Nullable final TimeZone timeZone) { if (m_aCronEx != null) m_aCronEx.setTimeZone (timeZone); m_aTimeZone = timeZone; } /** *

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

*

* Note that the date returned is NOT validated against the related * {@link ICalendar} (if any) *

* * @param aAfterTime * after time */ public Date getFireTimeAfter (@Nullable final Date aAfterTime) { Date afterTime = aAfterTime; if (afterTime == null) afterTime = new Date (); if (getStartTime ().after (afterTime)) { afterTime = new Date (getStartTime ().getTime () - 1000l); } if (getEndTime () != null && (afterTime.compareTo (getEndTime ()) >= 0)) { return null; } final Date pot = getTimeAfter (afterTime); if (getEndTime () != null && pot != null && pot.after (getEndTime ())) { return null; } return pot; } /** *

* NOT YET IMPLEMENTED: Returns the final time at which the * CronTrigger will fire. *

*

* Note that the return time *may* be in the past. and the date returned is * not validated against {@link ICalendar} *

*/ public Date getFinalFireTime () { final Date resultTime; if (getEndTime () != null) resultTime = getTimeBefore (new Date (getEndTime ().getTime () + 1000l)); else resultTime = m_aCronEx == null ? null : m_aCronEx.getFinalFireTime (); if (resultTime != null && getStartTime () != null && resultTime.before (getStartTime ())) return null; return resultTime; } /** * Determines whether or not the CronTrigger will occur again. */ public boolean mayFireAgain () { return getNextFireTime () != null; } @Override protected boolean validateMisfireInstruction (final EMisfireInstruction misfireInstruction) { switch (misfireInstruction) { case MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY: case MISFIRE_INSTRUCTION_SMART_POLICY: case MISFIRE_INSTRUCTION_FIRE_ONCE_NOW: case MISFIRE_INSTRUCTION_DO_NOTHING: return true; default: return false; } } /** *

* Updates the CronTrigger's state based on the * MISFIRE_INSTRUCTION_XXX that was selected when the CronTrigger * 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
  • *
*/ public void updateAfterMisfire (final ICalendar cal) { EMisfireInstruction instr = getMisfireInstruction (); if (instr == EMisfireInstruction.MISFIRE_INSTRUCTION_SMART_POLICY) { // What's smart here instr = EMisfireInstruction.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW; } switch (instr) { case MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY: return; case MISFIRE_INSTRUCTION_FIRE_ONCE_NOW: { setNextFireTime (new Date ()); break; } case MISFIRE_INSTRUCTION_DO_NOTHING: { Date newFireTime = getFireTimeAfter (new Date ()); while (newFireTime != null && cal != null && !cal.isTimeIncluded (newFireTime.getTime ())) { newFireTime = getFireTimeAfter (newFireTime); } setNextFireTime (newFireTime); break; } } } /** *

* Determines whether the date and (optionally) time of the given Calendar * instance falls on a scheduled fire-time of this trigger. *

*

* Equivalent to calling willFireOn(cal, false). *

* * @param test * the date to compare * @see #willFireOn(Calendar, boolean) */ public boolean willFireOn (final Calendar test) { return willFireOn (test, false); } /** *

* Determines whether the date and (optionally) time of the given Calendar * instance falls on a scheduled fire-time of this trigger. *

*

* Note that the value returned is NOT validated against the related * {@link ICalendar} (if any) *

* * @param aTest * the date to compare * @param dayOnly * if set to true, the method will only determine if the trigger will * fire during the day represented by the given Calendar (hours, * minutes and seconds will be ignored). * @see #willFireOn(Calendar) */ public boolean willFireOn (final Calendar aTest, final boolean dayOnly) { final Calendar test = QCloneUtils.getClone (aTest); // don't compare millis. test.set (Calendar.MILLISECOND, 0); if (dayOnly) { test.set (Calendar.HOUR_OF_DAY, 0); test.set (Calendar.MINUTE, 0); test.set (Calendar.SECOND, 0); } final Date testTime = test.getTime (); Date fta = getFireTimeAfter (new Date (test.getTime ().getTime () - 1000)); if (fta == null) return false; final Calendar p = Calendar.getInstance (test.getTimeZone (), Locale.getDefault (Locale.Category.FORMAT)); p.setTime (fta); final int year = p.get (Calendar.YEAR); final int month = p.get (Calendar.MONTH); final int day = p.get (Calendar.DATE); if (dayOnly) { return (year == test.get (Calendar.YEAR) && month == test.get (Calendar.MONTH) && day == test.get (Calendar.DATE)); } while (fta.before (testTime)) { fta = getFireTimeAfter (fta); } return fta.equals (testTime); } /** *

* Called when the {@link com.helger.quartz.IScheduler} 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(com.helger.quartz.IJobExecutionContext, * com.helger.quartz.JobExecutionException) */ @Override public void triggered (final ICalendar calendar) { m_aPreviousFireTime = m_aNextFireTime; m_aNextFireTime = getFireTimeAfter (m_aNextFireTime); while (m_aNextFireTime != null && calendar != null && !calendar.isTimeIncluded (m_aNextFireTime.getTime ())) { m_aNextFireTime = getFireTimeAfter (m_aNextFireTime); } } /** * @see AbstractTrigger#updateWithNewCalendar(ICalendar, long) */ public void updateWithNewCalendar (final ICalendar calendar, final long misfireThreshold) { m_aNextFireTime = getFireTimeAfter (m_aPreviousFireTime); if (m_aNextFireTime == null || calendar == null) { return; } final Date now = new Date (); while (m_aNextFireTime != null && !calendar.isTimeIncluded (m_aNextFireTime.getTime ())) { m_aNextFireTime = getFireTimeAfter (m_aNextFireTime); if (m_aNextFireTime == null) break; // avoid infinite loop // Use gregorian only because the constant is based on Gregorian final Calendar c = PDTFactory.createCalendar (); c.setTime (m_aNextFireTime); if (c.get (Calendar.YEAR) > YEAR_TO_GIVEUP_SCHEDULING_AT) { m_aNextFireTime = null; } if (m_aNextFireTime != null && m_aNextFireTime.before (now)) { final long diff = now.getTime () - m_aNextFireTime.getTime (); if (diff >= misfireThreshold) { m_aNextFireTime = getFireTimeAfter (m_aNextFireTime); } } } } /** *

* 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 (final ICalendar calendar) { m_aNextFireTime = getFireTimeAfter (new Date (getStartTime ().getTime () - 1000l)); while (m_aNextFireTime != null && calendar != null && !calendar.isTimeIncluded (m_aNextFireTime.getTime ())) { m_aNextFireTime = getFireTimeAfter (m_aNextFireTime); } return m_aNextFireTime; } public String getExpressionSummary () { return m_aCronEx == null ? null : m_aCronEx.getExpressionSummary (); } /** * Get a {@link IScheduleBuilder} that is configured to produce a schedule * identical to this trigger's schedule. * * @see #getTriggerBuilder() */ @Override public IScheduleBuilder getScheduleBuilder () { final CronScheduleBuilder cb = CronScheduleBuilder.cronSchedule (getCronExpression ()).inTimeZone (getTimeZone ()); switch (getMisfireInstruction ()) { case MISFIRE_INSTRUCTION_DO_NOTHING: cb.withMisfireHandlingInstructionDoNothing (); break; case MISFIRE_INSTRUCTION_FIRE_ONCE_NOW: cb.withMisfireHandlingInstructionFireAndProceed (); break; } return cb; } @Nullable protected Date getTimeAfter (final Date afterTime) { return m_aCronEx == null ? null : m_aCronEx.getTimeAfter (afterTime); } /** * NOT YET IMPLEMENTED: Returns the time before the given time that this * CronTrigger will fire. */ @Nullable protected Date getTimeBefore (final Date eTime) { return m_aCronEx == null ? null : m_aCronEx.getTimeBefore (eTime); } @Nonnull @ReturnsMutableCopy public CronTrigger getClone () { return new CronTrigger (this); } @Override public boolean equals (final Object o) { // New field, no change return super.equals (o); } @Override public int hashCode () { // New field, no change return super.hashCode (); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy