Rob Gordon is a seasoned Java developer and a big fan of open source. Oddjob is his own open source project to make job scheduling and task automation in Java much much easier. Rob is based in London. Rob is a DZone MVB and is not an employee of DZone and has posted 17 posts at DZone. You can read more from them at their website. View Full User Profile

Another Take on Java Scheduling

04.02.2012
| 5625 views |
  • submit to reddit

While many are looking forward to the holidays, those that must support batch based applications may not be so enthusiastic as they anticipate the numerous support calls for jobs that failed because they shouldn’t have run or for jobs that should have run but didn’t. It was after one such lost long weekend a decade ago that I decided to have a go at writing my own scheduling system.

Schedule Beans

With the growing trend for XML configuration I could see the potential for very expressive schedules by creating lots of little schedule beans that could be plugged together. Here’s a very simple example that uses the DailySchedule bean.

DailySchedule mySchedule = new DailySchedule();
mySchedule.setAt("08:00");
 
ScheduleContext context = new ScheduleContext(new Date());
 
for (int i = 0; i < 7; ++i) {
    ScheduleResult result = mySchedule.nextDue(context);
    System.out.println("Next due: " + result.getFromDate());
    context = context.move(result.getUseNext());
}

Hopefully you get the gist of how it works. We use the result of one schedule calculation to move the calculation on, and do this in a loop to get through the week. Here’s the output:

Next due: Thu Mar 29 08:00:00 BST 2012
Next due: Fri Mar 30 08:00:00 BST 2012
Next due: Sat Mar 31 08:00:00 BST 2012
Next due: Sun Apr 01 08:00:00 BST 2012
Next due: Mon Apr 02 08:00:00 BST 2012
Next due: Tue Apr 03 08:00:00 BST 2012
Next due: Wed Apr 04 08:00:00 BST 2012

From Schedules to Scheduler

Our schedule can be used with a scheduler of your choice. Here it is with the standard Java Timer.

public class TimerScheduler {
 
    final Timer timer = new Timer();
 
    ScheduleContext context;
 
    public TimerScheduler(Date startFrom) {
        this.context = new ScheduleContext(startFrom);
    }
 
    void schedule(final Runnable job) {
 
        DailySchedule schedule = new DailySchedule();
        schedule.setAt("08:00");
        final ScheduleResult next = schedule.nextDue(context);
 
        System.out.println("Scheduled at " + next.getFromDate());
 
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                job.run();
                context = context.move(next.getUseNext());
                schedule(job);
            }
        }, next.getFromDate());
    }
}

Which is started like this.

TimerScheduler scheduler = new TimerScheduler(new Date());
scheduler.schedule(new Runnable()  {
            @Override
            public void run() {
                System.out.println("Hello");
            }
        });

Here’s the same schedule used with a java.util.concurrent.ScheduledExecutor:

public class ExecutorScheduler {
 
    final ScheduledExecutorService executor =
            Executors.newScheduledThreadPool(3);
 
    ScheduleContext context;
 
    public ExecutorScheduler(Date startFrom) {
        this.context = new ScheduleContext(startFrom);
    }
 
    void schedule(final Runnable job) {
 
        DailySchedule schedule = new DailySchedule();
        schedule.setAt("08:00");
        final ScheduleResult next = schedule.nextDue(context);
 
        System.out.println("Scheduled at " + next.getFromDate());
 
        long delay = next.getFromDate().getTime() -
                System.currentTimeMillis(); 
 
        executor.schedule(new Runnable() {
            @Override
            public void run() {
                job.run();
                context = context.move(next.getUseNext());
                schedule(job);
            }
        }, delay, TimeUnit.MILLISECONDS);
    }
}

This can be started in the same way as the Timer example.

Holidays

So how does this help with holidays? A BrokenSchedule provides a way of breaking up one schedule with another.

Here’s our daily schedule wrapped by a WeeklySchedule so it will only run from Monday to Friday.

DailySchedule dailySchedule = new DailySchedule();
dailySchedule.setAt("08:00");
 
WeeklySchedule weeklySchedule = new WeeklySchedule();
weeklySchedule.setFrom(DayOfWeek.Days.MONDAY);
weeklySchedule.setTo(DayOfWeek.Days.FRIDAY);
weeklySchedule.setRefinement(dailySchedule);

Plugged into our loop from the first example, to display the ‘next due’ dates, we see that it does indeed miss weekends:

Next due: Wed Apr 04 08:00:00 BST 2012
Next due: Thu Apr 05 08:00:00 BST 2012
Next due: Fri Apr 06 08:00:00 BST 2012
Next due: Mon Apr 09 08:00:00 BST 2012
Next due: Tue Apr 10 08:00:00 BST 2012

Our holidays are formed by a ScheduleList which is a schedule that is a composite of its child schedules.

DateSchedule goodFriday = new DateSchedule();
goodFriday.setOn("2012-04-06");
 
DateSchedule easterMonday = new DateSchedule();
easterMonday.setOn("2012-04-09");
 
ScheduleList holidayList = new ScheduleList();
holidayList.setSchedules(new Schedule[] {
            goodFriday, easterMonday });

We can now use the holidays to break our week day schedule.

BrokenSchedule brokenSchedule = new BrokenSchedule();
brokenSchedule.setSchedule(weeklySchedule);
brokenSchedule.setBreaks(holidayList);

And now our Easter Weekend looks like this:

Next due: Wed Apr 04 08:00:00 BST 2012
Next due: Thu Apr 05 08:00:00 BST 2012
Next due: Tue Apr 10 08:00:00 BST 2012
Next due: Wed Apr 11 08:00:00 BST 2012

Conclusion

My initial project was called TreeSched because the beans can be seen as specifying a tree of schedules where branches narrow to smaller and smaller intervals of time. The project has since been subsumed into Oddjob, but the API is still quite open and independent and available to anyone who needs schedules but not necessarily a scheduler.

For more information on Oddjob schedules please see the Oddjob Documentation and if you have any queries on using the API then please post them to the Oddjob Forum.

 

 

 

 

 

 

 

 

 

Published at DZone with permission of Rob Gordon, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)