Understanding Quartz Triggers
Jobs contain the logic for the task to perform, but a job knows nothing about the time that it should be executed. This knowledge is left for the trigger. A Quartz trigger extends the abstract org.quartz.Trigger class. Currently, three Quartz triggers are available:
- org.quartz.SimpleTrigger
- org.quartz.CronTrigger
- org.quartz.NthIncludedDayTrigger
There is a fourth trigger, called UICronTrigger, but it has been deprecated in Quartz 1.5. It was primarily used by the Quartz Web application and is not used within Quartz itself.
Using the org.quartz.SimpleTrigger
Appropriately named, the SimpleTrigger is the simplest Quartz trigger to set up and use. It's designed to be used for jobs that need to start at a particular date/time and to repeat n number of times with a possible delay between each execution. Listing 4.9 provides an example that uses a SimpleTrigger.
Listing 4.9. Using a SimpleTrigger to Schedule a Job
public class Listing_4_9 { static Log logger = LogFactory.getLog(Listing_4_9.class); public static void main(String[] args) { Listing_4_9 example = new Listing_4_9(); example.startScheduler(); } public void startScheduler() { try { // Create and start the scheduler Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start(); logger.info("Scheduler has been started"); JobDetail jobDetail = new JobDetail("PrintInfoJob", Scheduler.DEFAULT_GROUP, PrintInfoJob.class); /* * Create a SimpleTrigger that starts immediately, * with a null end date/time, repeats forever and has * 1 minute (60000 ms) between each firing. */ Trigger trigger = new SimpleTrigger("myTrigger", Scheduler.DEFAULT_GROUP, new Date(), null, SimpleTrigger.REPEAT_INDEFINITELY, 60000L); scheduler.scheduleJob(jobDetail, trigger ); } catch (SchedulerException ex) { logger.error(ex); } } } |
Several variations of the SimpleTrigger constructor exist. They range from the no-argument version all the way to one that takes a full set of arguments. This code fragment shows a simple constructor that takes just the name and group for the trigger:
//No Argument Constructor SimpleTrigger sTrigger = new SimpleTrigger("myTrigger", Scheduler.DEFAULT_GROUP);
This trigger will execute immediately and will not repeat. There's also a constructor that takes several arguments and configures the trigger to fire at a particular time, repeat multiple times, and delay between each firing.
public SimpleTrigger(String name, String group, String jobName, String jobGroup, Date startTime, Date endTime, int repeatCount, long repeatInterval);
Using the org.quartz.CronTrigger
The CronTrigger allows for a much more complex firing schedule. Whereas you might have to use two or more SimpleTriggers to meet your firing needs, you might need just a single CronTrigger instance.
As the name implies, the CronTrigger is based on UNIX cron-like expressions. For example, you might have a job that needs to be executed every five minutes between 8:00 AM and 9:00 AM on Monday and Friday. If you tried to implement this with a SimpleTrigger, you would probably end up with several triggers for the job. However, you can use an expression like this to produce a trigger that will fire on this schedule:
"0 0/5 8 ? * MON,FRI" try { CronTrigger cTrigger = new CronTrigger("myTrigger", Scheduler.DEFAULT_GROUP, "0 0/5 8 ? * MON,FRI"); } catch (ParseException ex) { ex.printStackTrace(); }
Because CronTriggers have so much flexibility built into them by the nature of the almost limitless expressions that can be created, the next chapter focuses exclusively on everything you wanted to know about CronTriggers and cron expressions. Chapter 5, "CronTriggers and More," also presents a set of cookbook examples of how to create CronTriggers for specific firing schedules.
Using the org.quartz.NthIncludedDayTrigger
The org.quartz.NthIncludedDayTrigger is one of the newest triggers the Quartz development team added to the framework. It's designed to be used to execute a job on an nth day of every interval type. For example, if you needed to execute a job that performs invoicing on every 15th of the month, you could use NthIncludedDayTrigger to perform this feat. A Quartz Calendar can also be associated with the trigger to take weekends and holidays into account and move the days forward if necessary. The following fragment illustrates how to create a NthIncludedDayTrigger.
NthIncludedDayTrigger trigger = new NthIncludedDayTrigger( "MyTrigger", Scheduler.DEFAULT_GROUP); trigger.setN(15); trigger.setIntervalType( NthIncludedDayTrigger.INTERVAL_TYPE_MONTHLY);
Using Multiple Triggers for a Job
You are not forced to live with just a single trigger per job. If you need a more complex firing schedule, you can create multiple triggers and assign them to the same job. The Scheduler determines the proper execution schedule based on all the triggers for the job. Using multiple triggers for the same JobDetail is shown in the following method fragment:
try { // Create and start the scheduler Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start(); logger.info("Scheduler has been started"); JobDetail jobDetail = new JobDetail("PrintInfoJob", Scheduler.DEFAULT_GROUP, PrintInfoJob.class); // A trigger that fires every 5 seconds Trigger trigger1 = TriggerUtils.makeSecondlyTrigger("trigger1", 5000, SimpleTrigger.REPEAT_INDEFINITELY); // A trigger that fires every 10 minutes Trigger trigger2 = TriggerUtils.makeMinutelyTrigger("trigger2", 10, SimpleTrigger.REPEAT_INDEFINITELY); // Schedule job with first trigger scheduler.scheduleJob(jobDetail, trigger1); // Schedule job with second trigger scheduler.scheduleJob(jobDetail, trigger1); } catch (SchedulerException ex) { logger.error(ex); }
One Job per Trigger
Although a single JobDetail can support multiple triggers, a trigger can be assigned to only a single job. |
The Quartz Calendar
Don't confuse the Quartz Calendar object with the java.util.Calendar in the Java API. They are two different components and are used for two different purposes. As you are probably aware, Java's Calendar object is used for general-purpose Date and Time utilities; much of the functionality that used to reside in Java's Date class now resides within the Calendar classes.
The Quartz Calendar, on the other hand, is used exclusively to block out sections of time so that triggers are prevented from firing during those blocked-out periods. For example, let's assume that you work for a financial institution such as a bank. It's very common for banks to have many "bank holidays." Suppose you don't need (or want) the jobs to run on those days. You can accomplish this in one of several ways:
- Let your jobs run anyway. (This might mess things up for the bank.)
- Manually stop your jobs during the holidays. (Someone would need to be there to do it.)
- Create multiple triggers that don't include these days. (This would be time-consuming to set up and maintain.)
- Set up a bank holiday calendar that excludes these days. (Very easy to do!)
Although you could use each of these solutions to solve the problem, the Quartz Calendar is specifically designed for this.
The org.quartz.Calendar Interface
Quartz defines the org.quartz.Calendar interface that all Quartz Calendars must implement. It contains several methods, but these are the two most important ones:
public long getNextIncludedTime(long timeStamp); public boolean isTimeIncluded(long timeStamp);
As a Quartz job creator and developer, you don't necessarily need to be that familiar with the Calendar interface. It's mainly for situations for which the existing Calendars (those that come with Quartz) are not sufficient. Out of the box, Quartz includes many Calendar implementations that should fill your needs. Table 4.1 lists the Calendars that come with Quartz ready to use.
Calendar Name |
Class |
Usage |
---|---|---|
BaseCalendar |
org.quartz.impl.calendar.BaseCalendar |
Implements base functionality for more advanced Calendars. Implements the org.quartz.Calendar interface. |
WeeklyCalendar |
org.quartz.impl.calendar.WeeklyCalendar |
Excludes one or more days of the weekfor example, can be used to exclude weekends. |
MonthlyCalendar |
org.quartz.impl.calendar.MonthlyCalendar |
Excludes days of the monthfor example, can be used to exclude the last day of every month. |
AnnualCalendar |
org.quartz.impl.calendar.AnnualCalendar |
Excludes one or more days during the year. |
HolidayCalendar |
org.quartz.impl.calendar.HolidayCalendar |
Made especially to exclude holidays from the trigger. |
Using Quartz Calendars
To use a Quartz Calendar, you simply need to instantiate one, add your excluded dates to it, and then register it with the Scheduler. Finally, associate the Calendar instance with each trigger instance that you want to use the Calendar with.
Listing 4.10 shows an example of excluding bank holidays using the Quartz AnnualCalendar class.
Listing 4.10. Using the AnnualCalendar to Exclude Bank Holidays
public class Listing_4_10 { static Log logger = LogFactory.getLog(Listing_4_10.class); public static void main(String[] args) { Listing_4_10 example = new Listing_4_10(); example.startScheduler(); } public void startScheduler() { try { // Create and start the scheduler Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start(); scheduleJob(scheduler, PrintInfoJob.class); logger.info("Scheduler starting up..."); scheduler.start(); } catch (SchedulerException ex) { logger.error(ex); } } private void scheduleJob(Scheduler scheduler, Class jobClass) { try { // Create an instance of the Quartz AnnualCalendar AnnualCalendar cal = new AnnualCalendar(); // exclude July 4th Calendar gCal = GregorianCalendar.getInstance(); gCal.set(Calendar.MONTH, Calendar.JULY); gCal.set(Calendar.DATE, 4); cal.setDayExcluded(gCal, true); // Add to scheduler, replace existing, update triggers scheduler. addCalendar("bankHolidays", cal, true, true); /* * Set up a trigger to start firing now, repeat forever * and have (60000 ms) between each firing. */ Trigger trigger = TriggerUtils.makeImmediateTrigger("myTrigger", -1,60000); // Trigger will use Calendar to exclude firing times trigger.setCalendarName("bankHolidays"); JobDetail jobDetail = new JobDetail(jobClass.getName(), Scheduler.DEFAULT_GROUP, jobClass); // Associate the trigger with the job in the scheduler scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException ex) { logger.error(ex); } } } |
When you run the example from Listing 4.10, unless it's July 4, you should see the job execute. As an exercise that's left for you to do, change the excluded date in the scheduleJob() method to the current date that you're reading this. If you run the code again, you should see that the current date has been excluded and the next firing time will be tomorrow.
Creating Your Own Calendars
This last section demonstrates how easy it is to create your own Calendar class. Suppose that you need a Calendar to exclude certain minutes of the hour. For example, suppose you need to exclude the first five minutes of every hour or the last 15 minutes of every hour. You can create a new Calendar to support this functionality.
We Could Probably Use a CronTrigger
We could probably come up with a cron expression to exclude these times, but that takes the fun out of creating a new Calendar class. |
Listing 4.11 shows the HourlyCalendar that we can use to exclude sets of minutes from the hour.
Listing 4.11. The HourlyCalendar Can Exclude Certain Minutes from Every Hour
public class HourlyCalendar extends BaseCalendar { // Array of Integer from 0 to 59 private List excludedMinutes = new ArrayList(); public HourlyCalendar() { super(); } public HourlyCalendar(Calendar baseCalendar) { super(baseCalendar); } public List getMinutesExcluded() { return excludedMinutes; } public boolean isMinuteExcluded(int minute) { Iterator iter = excludedMinutes.iterator(); while (iter.hasNext()) { Integer excludedMin = (Integer) iter.next(); if (minute == excludedMin.intValue()) { return true; } continue; } return false; } public void setMinutesExcluded(List minutes) { if (minutes == null) return; excludedMinutes.addAll(minutes); } public void setMinuteExcluded(int minute) { if (isMinuteExcluded(minute)) return; excludedMinutes.add(new Integer(minute)); } public boolean isTimeIncluded(long timeStamp) { if (super.isTimeIncluded(timeStamp) == false) { return false; } java.util.Calendar cal = getJavaCalendar(timeStamp); int minute = cal.get(java.util.Calendar.MINUTE); return !(isMinuteExcluded(minute)); } public long getNextIncludedTime(long timeStamp) { // Call base calendar implementation first long baseTime = super.getNextIncludedTime(timeStamp); if ((baseTime > 0) && (baseTime > timeStamp)) timeStamp = baseTime; // Get timestamp for 00:00:00 long newTimeStamp = buildHoliday(timeStamp); java.util.Calendar cal = getJavaCalendar(newTimeStamp); int minute = cal.get(java.util.Calendar.MINUTE); if (isMinuteExcluded(minute) == false) return timeStamp; // return the // original value while (isMinuteExcluded(minute) == true) { cal.add(java.util.Calendar.MINUTE, 1); } return cal.getTime().getTime(); } } |
If you use the HourlyCalendar to schedule a job, all you need to do is set the minutes of the hour that you want to exclude; the Calendar and the Scheduler do the rest. You can see the HourlyCalendar demonstrated in Listing 4.12.
Listing 4.12. The HourlyCalendar Executes Based on Certain Excluded Minutes of the Hour
public class Listing_4_12 { static Log logger = LogFactory.getLog(Listing_4_12.class); public static void main(String[] args) { Listing_4_12 example = new Listing_4_12(); example.startScheduler(); } public void startScheduler() { try { // Create a default instance of the Scheduler Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // Using the NoOpJob, but could have been any scheduleJob(scheduler, PrintInfoJob.class); logger.info("Scheduler starting up..."); scheduler.start(); } catch (SchedulerException ex) { logger.error(ex); } } private void scheduleJob(Scheduler scheduler, Class jobClass) { try { // Create an instance of the Quartz AnnualCalendar HourlyCalendar cal = new HourlyCalendar(); cal.setMinuteExcluded(47); cal.setMinuteExcluded(48); cal.setMinuteExcluded(49); cal.setMinuteExcluded(50); // Add Calendar to the Scheduler scheduler. addCalendar("hourlyExample", cal, true, true); Trigger trigger = TriggerUtils.makeImmediateTrigger("myTrigger", -1, 10000); // Trigger will use Calendar to exclude firing times trigger.setCalendarName("hourlyExample"); JobDetail jobDetail = new JobDetail(jobClass.getName(), Scheduler.DEFAULT_GROUP, jobClass); // Associate the trigger with the job in the scheduler scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException ex) { logger.error(ex); } } } |
When you run Listing 4.12, you should see that the PrintInfoJob is not executed during the excluded minutes. Change the minutes that are excluded using the setMinuteExcluded() method and see for yourself how the new Calendar works.