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.
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
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.
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
|