Friday, July 15, 2005

Running regular, periodic jobs with Quartz

Many applications have the need to run regular, periodic jobs/processes. Such is the case with the application I'm working on. One option is to write your own job processing engine, but why reinvent the wheel. Another option is to use classes provided in the Java SDK, java.util.Timer and TimerTask, but there are limitations here. For example, there is a very limited set of scheduling mechanisms and the timers don't use a thread pool.

I came across the Quartz Job Scheduler, an open-source project by OpenSymphony. What a great product! I have integrated it into two different Struts-based web applications and it was quite simple to do.

There are a couple of ways to integrate it. One way is by adding a reference to the Quartz servlet in your web.xml file. I did have a couple of problems with this approach, so I opted for integrating using a Struts plugin. The problem I encountered with the first approach was that I couldn't get the Scheduler to stop running after I stopped my servlet container (Tomcat 5.0.x). I suspect it may have been user error. If not, the problem could well have been resolved with the later releases.

The plugin approach was quite simple. Add a line like this to your struts-config.xml file:

<plug-in className="com.mycompany.plugin.QuartzPlugin"/>

Then create the QuartzPlugin class. It may look something like this:

public class QuartzPlugin implements PlugIn {
private String MY_JOB_GROUP = "MyJobGroup";
Scheduler sched = null;

public void init(ActionServlet servlet, ModuleConfig moduleConfig) throws ServletException {
log.info("Quartz starting");

try {
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
sched = schedFact.getScheduler();
sched.start();

// Register job listeners with the scheduler
sched.addJobListener(new EmailWhenJobCompletesListener());

// Create the MyJob
JobDetail jobDetail = new JobDetail("MyJob", MY_JOB_GROUP, MyJob.class);

// Auto delete job from scheduler when no active triggers are associated with it.
jobDetail.setDurability(false);

// Do not persist between restarts of scheduler.
jobDetail.setVolatility(true);

// Pass stuff to the job with the JobDataMap.
String basePath = servlet.getServletContext().getRealPath("/");
jobDetail.getJobDataMap().put("basePath", basePath);
jobDetail.getJobDataMap().put("emailRecipients", MyConstants.JOB_STATUS_EMAIL_ADDRESS);
jobDetail.getJobDataMap().put("jobName", "My Job");

// Assign the EmailWhenJobCompletesListener to listen to this job and
// send an email when it is done.
jobDetail.addJobListener(EmailWhenJobCompletesListener.LISTENER_NAME);

// This is a trigger that will fire every night at 2:00am.
String cronExpression = "0 0 2 * * ?";
CronTrigger trigger = new CronTrigger("MyTrigger",MY_JOB_GROUP, "MyJob", MY_JOB_GROUP, cronExpression);

try {
// Schedule the job with the Scheduler.
sched.scheduleJob(jobDetail, trigger);
log.info("Scheduled the MyJob - " + cronExpression);
} catch (SchedulerException e) {
log.info("Quartz Scheduler failed to schedule the My Job: " + e.toString());
throw new ServletException(e);
}

} catch (Exception e) {
log.info("Quartz Scheduler failed to initialize: " + e.toString());
throw new ServletException(e); }
log.debug("Quartz started");
}

public void destroy() {
log.info("Quartz stopping");

try {
sched.shutdown();
} catch (SchedulerException ex) {
ex.printStackTrace();
}

sched = null;
}
}


And finally, create the job class, i.e., the class that will do what you want the job to do.

public class MyJob implements Job {
public MyJob() { }
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
// Get a variable I passed to the job.
String basePath = dataMap.getString("basePath");
// Do stuff...