Any time, any place, any where.
Scheduling in Oddjob is achieved by inserting a job into the job tree that asks Oddjob to execute its child job later, but watches its child's completion state. There are three scheduling jobs:
The Timer is driven by a schedule, The detail of these schedules is discussed in the next section. Setting aside the details of the schedules, we can focus on the mechanism of timers with an example:
<oddjob xmlns:scheduling="http://rgordon.co.uk/oddjob/scheduling" xmlns:schedules="http://rgordon.co.uk/oddjob/schedules"> <job> <sequential> <jobs> <scheduling:timer id="timer1" name="Morning Timer"> <schedule> <schedules:daily to="12:00"> <refinement> <schedules:interval interval="00:15"/> </refinement> </schedules:daily> </schedule> <job> <echo name="Morning Job">Good Morning.</echo> </job> </scheduling:timer> <scheduling:timer id="timer2" name="Afternoon Timer"> <schedule> <schedules:daily from="12:00"> <refinement> <schedules:interval interval="00:10"/> </refinement> </schedules:daily> </schedule> <job> <echo name="Afternoon Job">Good Afternoon.</echo> </job> </scheduling:timer> </jobs> </sequential> </job> </oddjob>
This creates two Timers, one that runs all morning, and one that runs all afternoon. Each Timer has a job that it will execute. The Timers are started using a Sequential Job. Starting a timer is quick operation. The timer calculates the next scheduled time and then submits the child job to a background execution service for later or immediate execution. It then enters an ACTIVE state and displays a sleeping icon. The sequential job will then start the second timer.
If Oddjob is being run with a persister, the timer will save its last completed time after each successful execution. If The timer is stopped and restarted this time is used to calculate the next execution time.
If the Timer is using persistence then the skipMissedRuns property would be used to see if it should skip its missed slots. By default if this sequence was started at 13:00 and it had been stopped after the 11:00 job, the Morning Timer would immediately run the 11:15 job followed by the 11:30 job followed by the 11:45 job - and simultaneously the Afternoon Timer would run the 12:00 job, followed by the 12:15 job etc.
a Retry job can be used on its own to retry something or it can be used within a Timer or Trigger schedule. Here's an example of it being used as the child job of a Timer:
<oddjob xmlns:scheduling="http://rgordon.co.uk/oddjob/scheduling" xmlns:schedules="http://rgordon.co.uk/oddjob/schedules" xmlns:state="http://rgordon.co.uk/oddjob/state"> <job> <sequential> <jobs> <scheduling:timer id="timer"> <schedule> <schedules:time from="07:00" to="14:00"/> </schedule> <job> <scheduling:retry limits="${timer.current}"> <schedule> <schedules:interval interval="00:15"/> </schedule> <job> <state:flag name="Naughty Job" state="INCOMPLETE"/> </job> </scheduling:retry> </job> </scheduling:timer> </jobs> </sequential> </job> </oddjob>
In this example the retry schedule is limited by they schedule of its parent timer. The Retry will keep trying the job every 15 minutes until the job completes or 2pm. At this point the retry will terminate in an INCOMPLETE state. The main Timer will then reschedule it at 7am the next day.
Because the Retry terminates in an INCOMPLETE state, it's possible to use a trigger schedule to fire an alert if required.
Holidays can be accounted for using a broken schedule. Here's an example.
<oddjob xmlns:scheduling="http://rgordon.co.uk/oddjob/scheduling" xmlns:schedules="http://rgordon.co.uk/oddjob/schedules"> <job> <sequential> <jobs> <variables id="global"> <ukHolidays> <schedules:list> <schedules> <schedules:date on="2009-01-01"/> <schedules:date on="2009-04-10"/> <schedules:date on="2009-04-13"/> <schedules:date on="2009-05-04"/> <schedules:date on="2009-05-25"/> <schedules:date on="2009-08-31"/> <schedules:date on="2009-12-25"/> <schedules:date on="2009-12-28"/> <schedules:date on="2010-01-01"/> <schedules:date on="2010-04-02"/> <schedules:date on="2010-04-05"/> <schedules:date on="2010-05-03"/> <schedules:date on="2010-05-31"/> <schedules:date on="2010-08-30"/> <schedules:date on="2010-12-27"/> <schedules:date on="2010-12-28"/> <schedules:date on="2011-01-03"/> <schedules:date on="2011-04-22"/> <schedules:date on="2011-04-25"/> <schedules:date on="2011-05-02"/> <schedules:date on="2011-05-30"/> <schedules:date on="2011-08-29"/> <schedules:date on="2011-12-27"/> <schedules:date on="2011-12-26"/> </schedules> </schedules:list> </ukHolidays> </variables> <scheduling:timer id="timer"> <schedule> <schedules:broken> <schedule> <schedules:daily from="07:00"/> </schedule> <breaks> <value value="${global.ukHolidays}"/> </breaks> </schedules:broken> </schedule> <job> <echo id="a-job">Work Work Work!</echo> </job> </scheduling:timer> </jobs> </sequential> </job> </oddjob>
Both the Timer and Retry can be given a time zone. If this is the case, the schedule will be evaluated in that time zone, regardless of the time zone of the server.
Time zones can be particularly useful when scheduling a download from a server in a different geographical location. By using their time zone the schedule will automatically account for their daylight saving time and will schedule holidays that span their day not your local day.
Time zones can also be useful when using a server in a different region as the backup server. By using time zones for schedules it is possible to stop your local server, copy all your configuration files to the backup server and start up Oddjob with no impact on scheduled times.
Time Zones can also be used to provide Summer Time (or Daylight Saving Time) configuration. To schedule without daylight saving time use a time zone that is fixed such as 'GMT+08'.
The trigger is a schedule that is job, rather than time, dependent. The trigger schedule will run a job when a second job enters a specified state. Triggers are particularly useful for firing alerts.
A trigger schedule can also be used in conjunction with a sequential job and a trigger job to provide a schedule that fires only when several jobs have completed.
The trigger schedule is relatively easy to configure. Please see the reference documentation for more information.
The natural cascading nature of job execution through the use of sequential and parallel jobs means that dependencies within scheduling aren't required as often as they would in other scheduling systems.
Here's an example that could have been constructed using a single Retry but has been contrived to demonstrate using a Trigger to schedule with dependences.
<oddjob id="this" xmlns:scheduling="http://rgordon.co.uk/oddjob/scheduling" xmlns:schedules="http://rgordon.co.uk/oddjob/schedules"> <job> <sequential> <jobs> <scheduling:trigger on="${both-files-available}"> <job> <echo id="a-job">Finally I get to do some work.</echo> </job> </scheduling:trigger> <parallel id="both-files-available"> <jobs> <scheduling:retry> <schedule> <schedules:interval interval="00:00:05"/> </schedule> <job> <exists file="${this.dir}/file1.txt"/> </job> </scheduling:retry> <scheduling:retry> <schedule> <schedules:interval interval="00:00:05"/> </schedule> <job> <exists file="${this.dir}/file2.txt"/> </job> </scheduling:retry> </jobs> </parallel> </jobs> </sequential> </job> </oddjob>
The trigger will fire when both files are available.