Scheduling the Quartz ScanDirectoryJob

So far, we've created a Quartz job but haven't determined what to do with it. We obviously need a way to set a schedule for the job to run. The schedule could be a one-time event, or we might need the job to run at midnight every night except Sunday. As you'll shortly see, the Quartz Scheduler is the heart and soul of the framework. All jobs are registered with the Scheduler; when necessary, the Scheduler also creates an instance of the Job class and invokes the execute() method.

Scheduler Creates New Job Instances for Each Execution

The Scheduler creates a new instance of the Job class for every execution. This means that any state that you have in instance variables is lost between job executions. You can choose to make a job stateful to persist job state between executions. The name stateful (especially in the J2EE world) has somewhat of a negative connotation associated with it, but with Quartz, a stateful job doesn't have the overhead and is quite easy to configure. When you are making a Quartz job stateful, however, a few things are unique to Quartz. Most important is that no two instances of the same stateful job class may execute concurrently. This could affect the scalability of the application. These issues and more are discussed in detail in the next chapter.

 

Creating and Running the Quartz Scheduler

Before we talk about the ScanDirectoryJob specifically, let's discuss in general how to instantiate and run an instance of the Quartz Scheduler. Listing 3.3 illustrates the basic steps necessary to create and start a Quartz Scheduler instance.

Listing 3.3. Running a Simplified Quartz Scheduler

package org.cavaness.quartzbook.chapter3;

package org.cavaness.quartzbook.chapter3;

import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;

public class SimpleScheduler {
 static Log logger = LogFactory.getLog(SimpleScheduler.class);

 public static void main(String[] args) {
 SimpleScheduler simple = new SimpleScheduler();
 simple.startScheduler();
 }

 public void startScheduler() {
 Scheduler scheduler = null;

 try {
 // Get a Scheduler instance from the Factory
 scheduler = StdSchedulerFactory.getDefaultScheduler();

 // Start the scheduler
 scheduler.start();
 logger.info("Scheduler started at " + new Date());

 } catch (SchedulerException ex) {
 // deal with any exceptions
 logger.error(ex);
 }
 }
}

If you run the code in Listing 3.3 and you are logging the output, you should see something like the following output:

INFO [main] (SimpleScheduler.java:30) - Scheduler started at Mon Sep 05 13:06:38 EDT 2005

Turning Off Quartz Info Log Messages

If you are using the Commons Logging framework along with Log4J, as the examples throughout this book do, you might need to turn off info log messages for everything but the examples in this book. This is because Quartz logs a sizable amount of debug and info messages. When you understand what Quartz is doing, the messages that you care more about can get lost in the volumes of log messages. You can do this by creating a log4j.properties file that logs only ERROR messages, and setting the messages that come from the examples in the book to show INFO messages. Here's an example log4j.properties file that will accomplish this:


 

[View full width]

# Create stdout appender log4j.rootLogger=error, stdout # Configure the stdout appender to go to the Console log4j.appender.stdout=org.apache.log4j.ConsoleAppender # Configure stdout appender to use the PatternLayout log4j.appender.stdout.layout=org.apache.log4j.PatternLayout # Pattern output the caller's filename and line # log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n # Print messages of level INFO or above for examples log4j.logger.org.cavaness.quartzbook=INFO  

This log4j.properties file will log only ERROR messages to stdout, but any INFO messages coming from the package org.cavaness.quartzbook will appear. This is due to the last line in the properties file.

Listing 3.3 shows just how simple it is to startup a Quartz scheduler. When the Scheduler is up and running, you can do much with it and obtain a great deal of information from it. For example, you might need to schedule a few jobs or change the execution times of jobs already scheduled. You might need to put the Scheduler in stand-by mode and then later restart it so that it begins executing scheduled jobs again. While the Scheduler is in stand-by, no jobs are executed, even if they are supposed to be based on their scheduled times. Listing 3.4 shows how to put the Scheduler in stand-by mode and then unpause it so the Scheduler picks up where it left off.

Listing 3.4. Putting the Scheduler in Stand-By Mode

private void modifyScheduler(Scheduler scheduler) {

 try {
 if (!scheduler.isInStandbyMode()) {
 // pause the scheduler
 scheduler.standby();
 }

 // Do something interesting here

 // and then restart it
 scheduler.start();

 } catch (SchedulerException ex) {
 logger.error(ex);
 }
}

The partial fragment in Listing 3.4 is just a quick illustration that when you have a reference to a Quartz Scheduler, you can do some pretty interesting things with it. Of course, the Scheduler doesn't have to be in stand-by mode for interesting things to happen. For example, you can schedule new jobs and unschedule existing jobs, all with the Scheduler still running. We add to our repertoire of possibilities with a scheduler throughout the book.

As simple as these examples seem to be, there is a catch. We haven't specified any jobs to be executed or times for those jobs to execute. Although the Quartz Scheduler did start up and run in Listing 3.3, we didn't specify any jobs to be executed. That's what we discuss next.

Scheduling a Quartz Job Programmatically

All jobs that you want Quartz to execute must be registered with the Scheduler. In most situations, this should be done before the Scheduler is started. As promised earlier in this chapter, this is one area in which you get to choose either a declarative or a programmatic approach. First we show you how to do it programmatically; later in this chapter, we repeat this exercise with the declarative version.

For each job that is to be registered with the Scheduler, a JobDetail must be defined and associated with a Scheduler instance. This is shown in Listing 3.5.

Listing 3.5. Scheduling a Job Programmatically

package org.cavaness.quartzbook.chapter3;

import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;

public class Listing_3_5 {
 static Log logger = LogFactory.getLog(Listing_3_5.class);

 public static void main(String[] args) {
 Listing_3_5 example = new Listing_3_5();

 try {
 // Create a Scheduler and schedule the Job
 Scheduler scheduler = example.createScheduler();
 example.scheduleJob(scheduler);

 // Start the Scheduler running
 scheduler.start();

 logger.info( "Scheduler started at " + new Date() )

 } catch (SchedulerException ex) {
 logger.error(ex);
 }
 }

 /*
 * return an instance of the Scheduler from the factory
 */
 public Scheduler createScheduler() throws SchedulerException {
 return StdSchedulerFactory.getDefaultScheduler();
 }

 // Create and Schedule a ScanDirectoryJob with the Scheduler
 private void scheduleJob(Scheduler scheduler)
 throws SchedulerException {

 // Create a JobDetail for the Job
 JobDetail jobDetail =
 new JobDetail("ScanDirectory",
 Scheduler.DEFAULT_GROUP,
 ScanDirectoryJob.class);

 // Configure the directory to scan
 jobDetail.getJobDataMap().put("SCAN_DIR",
 "c:\quartz-book\input");

 // Create a trigger that fires every 10 seconds, forever
 Trigger trigger = TriggerUtils.makeSecondlyTrigger(10);
 trigger.setName("scanTrigger");
 // Start the trigger firing from now
 trigger.setStartTime(new Date());

 // Associate the trigger with the job in the scheduler
 scheduler.scheduleJob(jobDetail, trigger);
 }
}

The program in Listing 3.5 provides a good example of how to programmatically schedule a job. The code first calls the createScheduler() method to obtain an instance of the Scheduler from the Scheduler factory. When a Scheduler is obtained, it is passed into the schedulerJob(), which takes care of all the details of associating a job with a Scheduler.

First, a JobDetail object is created for the job that we want to run. The arguments in the constructor for the JobDetail include a name for the job, a logical group to assign the job to, and the fully qualified class that implements the org.quartz.Job interface. We could have used several different versions of the JobDetail constructor:

public JobDetail();
public JobDetail(String name, String group, Class
jobClass);
public JobDetail(String name, String group, Class jobClass,
 boolean volatility, boolean durability, boolean
recover);

Note

A job should be uniquely identifiable by its name and group within a Scheduler instance. If you add two jobs with the same name and group, an ObjectAlreadyExistsException will be thrown.

As stated earlier in this chapter, the JobDetail acts as a definition for a specific job. It contains properties for the job instance and also can be accessed by the Job class at runtime. One of the most important uses of the JobDetail is to hold the JobDataMap, which is used to store state/parameters for a job instance. In Listing 3.5, the name of the directory to scan is stored in the JobDataMap in the scheduleJob() method.

Understanding and Using Quartz Triggers

Jobs are only part of the equation. Notice from Listing 3.5 that we don't set the execution date and times for the job within the JobDetail object. This is done by using a Quartz trigger. As the name implies, triggers are responsible for triggering a job to be executed. You create a trigger and associate it with a job when registering the job with the Scheduler. Four types of Quartz triggers are available, but two main types are used most often and, thus, are used for the next several chapters: SimpleTrigger and CronTrigger.

A SimpleTrigger is the simpler of the two and is used primarily to fire single event jobs. This trigger fires at a given time, repeats for n number of times with a delay of m between each firing, and then quits. CronTriggers are much more complicated and powerful. They are based on the common Gregorian calendar and are used when you need to execute a job with a more complicated schedulefor example, every Monday, Wednesday, and Friday at midnight during the months of April and September.

To make working with triggers easier, Quartz includes a utility class called org.quartz.TriggerUtils. triggerUtils provides many convenience methods for simplifying the construction and configuration of triggers. The examples throughout this chapter use the triggerUtils class; SimpleTrigger and CronTrigger are used in later chapters.

As you can see from Listing 3.5, the triggerUtils method called makeSecondlyTrigger() is used to create a trigger that fires every 10 seconds (triggerUtils actually produces an instance of SimpleTrigger in this case, but our code doesn't care to know that). We must also give the trigger instance a name and tell it when to start firing; in Listing 3.5, this starts immediately because setStartTime() is set to the current time.

Listing 3.5 illustrates how to register a single job with the Scheduler. If you have more than one job (and you probably do), you will need to create a JobDetail for each job. Each one must be registered with the Scheduler via the scheduleJob() method.

Note

Go back to Listing 3.1 and look at the code where the scan directory property is retrieved from the JobDataMap. Based on Listing 3.5, you can see how it gets set.

If you are reusing a job class to run multiple instances of the same job, you need to create a JobDetail for each one. For example, if you wanted to reuse the ScanDirectoryJob to check two different directories, you would need to create and register two instances of the JobDetail class. Listing 3.6 shows how this could be done.

Listing 3.6. Running Multiple Instances of ScanDirectoryJob

package org.cavaness.quartzbook.chapter3;

import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;

public class Listing_3_6 {
 static Log logger = LogFactory.getLog(Listing_3_6.class);

 public static void main(String[] args) {
 Listing_3_6 example = new Listing_3_6();

 try {
 // Create a Scheduler and schedule the Job
 Scheduler scheduler = example.createScheduler();

 // Jobs can be scheduled after Scheduler is running
 scheduler.start();

 logger.info("Scheduler started at " + new Date());

 // Schedule the first Job
 example.scheduleJob(scheduler, "ScanDirectory1",
 ScanDirectoryJob.class,
 "c:\quartz-book\input", 10);

 // Schedule the second Job
 example.scheduleJob(scheduler, "ScanDirectory2",
 ScanDirectoryJob.class,
 "c:\quartz-book\input2", 15);

 } catch (SchedulerException ex) {
 logger.error(ex);
 }
 }

 /*
 * return an instance of the Scheduler from the factory
 */
 public Scheduler createScheduler() throws SchedulerException {
 return StdSchedulerFactory.getDefaultScheduler();
 }

 // Create and Schedule a ScanDirectoryJob with the Scheduler
 private void scheduleJob(Scheduler scheduler, String jobName,
 Class jobClass, String scanDir, int scanInterval)
 throws SchedulerException {

 // Create a JobDetail for the Job
 JobDetail jobDetail =
 new JobDetail(jobName,
 Scheduler.DEFAULT_GROUP, jobClass);

 // Configure the directory to scan
 jobDetail.getJobDataMap().put("SCAN_DIR", scanDir);

 // Trigger that repeats every "scanInterval" secs forever
 Trigger trigger =
 TriggerUtils.makeSecondlyTrigger(scanInterval);

 trigger.setName(jobName + "-Trigger");

 // Start the trigger firing from now
 trigger.setStartTime(new Date());

 // Associate the trigger with the job in the scheduler
 scheduler.scheduleJob(jobDetail, trigger);
 }
}

Listing 3.6 is very similar to the program from Listing 3.5, with a few small differences. The main difference is that Listing 3.6 has been refactored to allow multiple calls to the schedulerJob() method. The settings for things such as the job name and the scan interval are being passed in. So after the Scheduler instance is obtained from the createScheduler() method, two jobs (of the same class) are scheduled using different arguments.

Scheduling Jobs Before or after the Scheduler Is Started

In the example in Listing 3.6, we called the start() on the Scheduler before we scheduled the jobs. Back in Listing 3.5, we did it the other way around: We called start() after the jobs were scheduled. Jobs and triggers can be added to and removed from the Scheduler at any time (except after shutdown() has been called on it).

 

Running the Program in Listing 3.6

If we execute the Listing_3_6 class, we should get output similar to the following:

INFO [main] (Listing_3_6.java:35) - Scheduler started at Mon Sep 05 15:12:15 EDT 2005
 INFO [QuartzScheduler_Worker-0] ScanDirectory1 fired at Mon Sep 05 15:12:15 EDT 2005
 INFO [QuartzScheduler_Worker-0] - c:quartz-bookinputorder-145765.xml - Size: 0
 INFO [QuartzScheduler_Worker-0] - ScanDirectory2 fired at Mon Sep 05 15:12:15 EDT 2005
 INFO [QuartzScheduler_Worker-0] - No XML files found in c:quartz-bookinput2
 INFO [QuartzScheduler_Worker-1] - ScanDirectory1 fired at Mon Sep 05 15:12:25 EDT 2005
 INFO [QuartzScheduler_Worker-1] - c:quartz-bookinputorder-145765.xml - Size: 0
 INFO [QuartzScheduler_Worker-3] - ScanDirectory2 fired at Mon Sep 05 15:12:30 EDT 2005
 INFO [QuartzScheduler_Worker-3] - No XML files found in c:quartz-bookinput2


Scheduling a Quartz Job Declaratively

Scheduling in the Enterprise

Getting Started with Quartz

Hello, Quartz

Scheduling Jobs

Cron Triggers and More

JobStores and Persistence

Implementing Quartz Listeners

Using Quartz Plug-Ins

Using Quartz Remotely

Using Quartz with J2EE

Clustering Quartz

Quartz Cookbook

Quartz and Web Applications

Using Quartz with Workflow

Appendix A. Quartz Configuration Reference



Quartz Job Scheduling Framework(c) Building Open Source Enterprise Applications
Quartz Job Scheduling Framework: Building Open Source Enterprise Applications
ISBN: 0131886703
EAN: 2147483647
Year: N/A
Pages: 148

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net