Quartz Scheduling with JBoss Seam

In this post we build on another and present a code framework with accompanying in-depth explanations that can be used to get you up and running with Quartz scheduling quickly.

You should also be able to incorporate this code into any Seam example application in no time and the in-depth explanations should help you further mould the framework to your requirements. For reference, we are using Seam 2.0.1.GA & JBoss 4.2.2.GA and we assume you have the Seam example applications handy although this will only become relevant when looking at the build.xml files in Step 2.

Quartz Background

In a nutshell, if you use a Unix-like operating system, and you know cron, then you know Quartz. This is an oversimplification of course, the extent to which you’ll be able to gauge if you have a look at the available Quartz Features and Quartz Tutorials, however the syntax used for calendar-like job scheduling by and large matches that of cron (see the CronTrigger Tutorial for more detail).

Step 1: Create the seam.quartz.properties File

In your resources folder, which contains your seam.properties file, amongst others, create a file alongside named seam.quartz.properties an drop the following text in it:

#==============================================================
# Configure Main Scheduler Properties
#==============================================================

org.quartz.scheduler.instanceName Sched1
org.quartz.scheduler.instanceId AUTO
org.quartz.scheduler.rmi.export false
org.quartz.scheduler.rmi.proxy false

#==============================================================
# Configure ThreadPool
#==============================================================

org.quartz.threadPool.class org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount 3

#==============================================================
# Configure JobStore
#==============================================================

org.quartz.jobStore.misfireThreshold 60000
org.quartz.jobStore.class org.quartz.simpl.RAMJobStore

Digging deeper

Lets consider what the above means:

  • org.quartz.scheduler.instanceName Sched1 – The Scheduler name, significant if you need to identify multiple Schedulers in your client code.
  • org.quartz.scheduler.instanceId AUTO – As can be inferred, this is an automatically generated Id for the Schedule instance.
  • org.quartz.scheduler.rmi.export false – With this set to true the Scheduler cannot be accessed remotely via RMI.
  • org.quartz.scheduler.rmi.proxy false – Only relevant, if you want to connect to a remotely served scheduler, we don’t so we set it to false.
  • org.quartz.threadPool.class org.quartz.simpl.SimpleThreadPool – Has an associated fixed-size pool of threads that live the lifetime of the Scheduler.
  • org.quartz.threadPool.threadCount 3 – The number of threads available for concurrent execution of jobs. Exactly how many threads one will need appears to be more of a thumb suck than exact science, but apparently if you only have a few jobs that fire a few times a day, then 1 thread is good enough, so we’ve thrown in two extras here for good measure. If you have too few threads though you may get a “mis-fire”, see the next property, the misfireThreshold, for more clarity on this.
  • org.quartz.jobStore.misfireThreshold 60000 – Accourding to the documentation, this is the number of milliseconds the scheduler will ‘tolerate’ a trigger to pass its next-fire-time by, before being considered “misfired”. What happens when a trigger misfires can be specified with a mis-fire instruction which is a property of the Quartz Trigger class. There is a default mis-fire instruction and the relevant section of the Quartz Scheduler Documentation recommends familiarity with the misfire instructions that are defined on the given trigger types, as explained in their JavaDoc, and also mentions that the misfire instruction for a given trigger instance can be configured using the setMisfireInstruction(..) method. One would think that some kind of logging or other corrective action would be appropriate in such a method.
  • org.quartz.jobStore.class org.quartz.simpl.RAMJobStore – with this all scheduling information  (job, triggers and calendars) is stored in memory and naturally all such information will be lost when the Scheduler terminates. A persistent alternative to this is JDBCJobStore.

Finally, for more information on Quartz configuration, please consult the Quartz Configuration Reference.

Step 2: Tweak your build.xml file

If you want to build any of the Seam example applications, you’ll notice a build file that has application specific options and then there is an import of an upper level build file. All you need to do is to add the following line into the example application build file (the details are in the imported build file if you are interested):

<property name="quartz.lib" value="yes"/>

Digging deeper

All this does is to result in your Quartz properties file going where it needs to go in terms of packaging, and likewise for the Quartz jar file. There is not much more one can say about this apart from that the Seam example build file structure can be quite handy, a lot of work has already gone into it so it seems to make sense to use it in your application. You may want to check up on what version of Quartz you are using.

Step 3: Edit the components.xml file

There’s very little to this file, located in your WEB-INF directory, just add in the following namespace scheme pointer in the components main node:

xmlns:async="http://jboss.com/products/seam/async"

Then add the following element to start the Quartz dispatcher:

<async:quartz-dispatcher/>

Step 4: Create the Processor and Controller classes

ScheduleProcessor.java:

package com.newco.services;

import java.util.Date;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.log.Log;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.annotations.async.Asynchronous;
import org.jboss.seam.annotations.async.Expiration;
import org.jboss.seam.annotations.async.IntervalCron;
import org.jboss.seam.async.QuartzTriggerHandle;

@Name("processor")
@AutoCreate
@Scope(ScopeType.APPLICATION)
public class ScheduleProcessor {

    @Logger
    private Log log;

    @Asynchronous
    @Transactional
    public QuartzTriggerHandle createQuartzTestTimer(
                @Expiration Date when, @IntervalCron String interval) {

    	String date = new Date().toString();
    	log.info( "Quartz Test: " + date );
    	return null;
    }
}

Digging deeper

As noted in the referenced post, this is where we place the work that needs to be done, or what we are actually scheduling. As you can see all we do here is to log that date periodically, with the period being defined by IntervalCron. Also note that the Processor class has an APPLICATION scope and is AutoCreate-ed, there are alternative ways one could achieve this (via an org.jboss.seam.postInitialization event) and which approach is superior, if any, is debatable.

ScheduleController.java:

package com.newco.services;

import java.io.Serializable;
import java.util.Date;
import static org.jboss.seam.ScopeType.APPLICATION;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.async.QuartzTriggerHandle;
import org.jboss.seam.log.Log;

@Name("controller")
@Scope(APPLICATION)
@AutoCreate
@Startup
public class ScheduleController implements Serializable {

    private static final long serialVersionUID = 7609983147081676186L;

    @In
    ScheduleProcessor processor;

    @Logger
    Log log;

    private QuartzTriggerHandle quartzTestTriggerHandle;
    private static String CRON_INTERVAL = "0 * * * * ?";

    @Create
    public void scheduleTimer() {
	    quartzTestTriggerHandle =
                processor.createQuartzTestTimer(new Date(), CRON_INTERVAL);
	}
}

Digging deeper

Our schedule is such that the trigger will fire every minute of every day so that we can see some action happening in the log in Step 5, for full coverage of alternatives and cron syntax, please consult the manual.

When looking at this, you might wonder what happened to the potential mis-fire instruction, where would it go if it where necessary? Also, with appropriate test cases, we would want to intentionally cause mis-fires so that our misfire instruction, that we had duly set via the setMisfireIntruction() method, can flex its muscles. These are important questions that we won’t answer in this post right now, but potentially in future.

Step 5: Deploy and watch your JBoss log file

All you need to do now is deploy the application, and if you watch the server log file, or console output, you should see something like this every minute:

12:55:00,134 INFO  [ScheduleProcessor] Quartz Test: Tue Dec 01 12:55:00 SAST 2009
12:56:00,029 INFO  [ScheduleProcessor] Quartz Test: Tue Dec 01 12:56:00 SAST 2009
12:57:00,025 INFO  [ScheduleProcessor] Quartz Test: Tue Dec 01 12:57:00 SAST 2009
12:58:00,025 INFO  [ScheduleProcessor] Quartz Test: Tue Dec 01 12:58:00 SAST 2009
12:59:00,031 INFO  [ScheduleProcessor] Quartz Test: Tue Dec 01 12:59:00 SAST 2009
<event type=”org.jboss.seam.postInitialization”><action execute=”#{controller.scheduleTimer}”/></event>
Advertisements