Lesson 4: More About Triggers
Like jobs, triggers are relatively easy to work with, but do contain a variety of customizable options that you need to be aware of and understand before you can make full use of Quartz. Also, as noted earlier, there are different types of triggers, that you can select to meet different scheduling needs.
Calendars
Quartz Calendar objects (not java.util.Calendar objects) can be associated with triggers at the time the trigger is stored in the scheduler. Calendars are useful for excluding blocks of time from the the trigger's firing schedule. For instance, you could create a trigger that fires a job every weekday at 9:30 am, but then add a Calendar that excludes all of the business's holidays.
Calendar's can be any serializable objects that implement the Calendar interface, which looks like this:
package org.quartz;
public interface Calendar {
public boolean isTimeIncluded(long timeStamp);
public long getNextIncludedTime(long timeStamp);
}
Notice that the parameters to these methods are of the long type. As you may guess, they are timestamps in millisecond format. This means that calendars can 'block out' sections of time as narrow as a millisecond. Most likely, you'll be interested in 'blocking-out' entire days. As a convenience, Quartz includes the class org.quartz.impl.HolidayCalendar, which does just that.
Calendars must be instantiated and registered with the scheduler via the addCalendar(..) method. If you use HolidayCalendar, after instantiating it, you should use its addExcludedDate(Date date) method in order to populate it with the days you wish to have excluded from scheduling. The same calendar instance can be used with multiple triggers such as this:
HolidayCalendar cal = new HolidayCalendar();
cal.addExcludedDate( someDate );
sched.addCalendar("myHolidays", cal, false);
Trigger trigger = TriggerUtils.makeHourlyTrigger(); trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date())); trigger.setName("myTrigger1");
trigger.setCalendarName("myHolidays");
Trigger trigger2 = TriggerUtils.makeDailyTrigger(8, 0); trigger.setStartTime(new Date()); trigger2.setName("myTrigger2");
trigger2.setCalendarName("myHolidays");
The details of the values passed in the SimpleTrigger constructors will be explained in the next section. For now, just believe that the code above creates two triggers: one that will repeat every 60 seconds forever, and one that will repeat five times with a five day interval between firings. However, any of the firings that would have occurred during the period excluded by the calendar will be skipped.
Priority
For each trigger, some kind of priority can be set. These priorities are not related to thread priorities (although you are free use these priorities to set thread priorities on your own), but to the order of firing triggers when the fire time has been reached.
When the scheduler is rather busy, some triggers are not able to fire at their next firing times. Therefore some queue of triggers arises, which have already passed their next firing time but are waiting to be fired. The queue is ordered according to the so-called "priority time" which equals the next firing time by default but can be set to other values. The earlier the priority time is, if compared to the next firing time, the more triggers can be overtaken. So the difference between the next firing time and priority time is a measure for the priority of a trigger. This difference (a number of milliseconds) can be set and obtained by the methods setPriorityMillis(long millis) and getPriorityMillis(), respectively. If the trigger fires more than once, the value of the difference does not change (unless you change the priority millis explicitly by calling setPriorityMillis(..) or setPriorityTime() below). Note that a trigger will never be fired before the next firing time, even if its priority time were years before the firing time. Here is an example:
long now = System.currentTimeMillis();
SimpleTrigger trigger1 = new SimpleTrigger("myTrigger1",
sched.DEFAULT_GROUP,
new Date(now+7000L),
null,
0,
0L);
SimpleTrigger trigger2 = new SimpleTrigger("myTrigger2",
sched.DEFAULT_GROUP,
new Date(now+14000L),
null,
0,
0L);
trigger2.setPriorityMillis(10L*1000L);
SimpleTrigger trigger3 = new SimpleTrigger("myTrigger3",
sched.DEFAULT_GROUP,
new Date(now+21000L),
null,
0,
0L);
trigger3.setPriorityMillis(12L*1000L);
When the scheduler is not busy and can fire these three triggers at their firing times, trigger1 fires first, trigger2 fires 7 seconds later and trigger3 fires 14 seconds after trigger1. When the three triggers are in a queue, they are ordered according to the priority times - they are 7 seconds resp. 4 seconds resp. 9 seconds after the current time, so that trigger2 will overtake trigger1, but trigger3 will not overtake trigger1 or trigger2.
Usually you would expect numbers as priorities and that the trigger of higher priority overtakes the trigger of lower priority. You can achieve this by setting the priority millis to the product of this priority number and a large, fixed value:
public static final long PRIORITY_THRESHOLD = 10L * 24L * 60L * 60L * 1000L;
t1.setPriorityMillis(4 * PRIORITY_THRESHOLD);
t2.setPriorityMillis(5 * PRIORITY_THRESHOLD);
In this example, trigger t2 will overtake t1 in the queue unless the next firing time of t1 is more than 10 days before the next firing time of t2! (It is very unusual to have triggers more than few hours in the queue.)
The usual system of numbers as priorities has the following disadvantage: When some trigger t is in the queue and regularly triggers of higher priorities arrive in the queue and overtake trigger t, the trigger t will never fire. In queuing theory, there are some concepts of raising the priority after some waiting time. This is automatically included in this approach - when in the example above the PRIORITY_THRESHOLD would only correspond to 2 hours, after 2 hours of waiting time in the queue, a trigger with priority 4 cannot be overtaken any more by an newly inserted trigger of priority 5 and after 4 hours of waiting cannot be overtaken any more by a newly inserted trigger of priority 6 etc.
Of course, the priority millis may also be negative (so it is easy to define a new trigger of lower priority than all already defined triggers). You may also set and obtain the priority time directly (using the methods setPriorityTime(Date priorityTime) and getPriorityTime()). When the trigger fires more than once, the priority time is always automatically adjusted so that the difference to the next firing time stays the same. Example: If a trigger fires every hour, then the priority time is shifted by 1 hour after every activation.
Take care about misfiring triggers when long waiting times in the queue are to be expected.
Misfire Instructions
Another important property of a Trigger is its "misfire instruction". A misfire occurs if a persistent trigger "misses" its firing time because of the scheduler being shutdown. The different trigger types have different misfire instructions available to them. By default they use a 'smart policy' instruction - which has dynamic behavior based on trigger type and configuration. When the scheduler starts, it searches for any persistent triggers that have misfired, and it then updates each of them based on their individually configured misfire instructions. When you start using Quartz in your own projects, you should make yourself familiar with the misfire instructions that are defined on the given trigger types, and explained in their JavaDOC. More specific information about misfire instructions will be given within the tutorial lessons specific to each trigger type. The misfire instruction for a given trigger instance can be configured using the setMisfireInstruction(..) method.
TriggerUtils - Triggers Made Easy
The TriggerUtils class (in the org.quartz package) contains conveniences to help you create triggers and dates without having to monkey around with java.util.Calendar objects. Use this class to easily make triggers that fire every minute, hour, day, week, month, etc. Also use this class to generate dates that are rounded to the nearest second, minute or hour - this can be very useful for setting trigger start-times.
TriggerListeners
Finally, triggers may have registered listeners, just as jobs may. Objects implementing the TriggerListener interface will receive notifications as a trigger is fired.
|