Quartz-CH3

advertisement
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
Chapter 3
Hello Quartz
Most readers benefit from a small but realistic walkthrough of an example. As a writer, it takes
some restraint to keep from unloading everything in a single chapter and as a reader it takes
some patience and a little leap of faith that the material is relevant and should be read,
regardless of the simplicity of the example.
With those thoughts in mind, this chapter introduces a simple quartz application that is
representative of the type of applications that you’ll create with the Quartz framework. The
chapter will walk you through the steps necessary to create and execute the example application.
When finished, you should have a solid foundation from which to build upon for the rest of the
chapters.
The Hello World Quartz Project
This example application will be a little more interesting than the proverbial
System.out.println( “Hello World from Quartz”). When a job is executed by
Quartz, we expect it to perform some interesting and useful task on our behalf. That’s what we
are going to do, something useful and interesting.
This chapter will show you how to create a Quartz job that will, when told to do so by our Quartz
application, will scan a specified directory and look for any and all XML files. If one or more
XML files are found within the specified directory, some cursory information about the file will
be printed out. How is this useful and interesting, you say? Hopefully, you can make the leap that
once your job is able to detect certain files in a directory; there are many interesting things to do
with those files. You might want to FTP them to a remote host or Email them as attachments.
Maybe the files are orders that were sent from a customer and need to be inserted into your
database. There are infinite possibilities and we’ll discuss some of the more interesting ones later
in the book.
All attempts will be made to keep this new material straight-forward and cover only the
essentials. However, we will be forced to investigate some of the common configuration settings
that affect the runtime behavior of a Quartz application. It’s expected that you know the Java
language well as very little time will be spent on explaining the standard aspects of it.
Finally, this chapter will wrap up with a brief discussion of how to package the example
application. Of course, Apache Ant is our choice for building and packaging Java projects and
Quartz applications are no exception.
Chapter 3, 1
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
Setting up the Quartz Project
The first step is to get the development environment setup for this project. You may choose
any development tool or IDE that makes you feel warm and fuzzy; Quartz won’t hold it against
you. However, if you’re part of the growing mass of Java developers that have finally succumb to
the peer pressure of other Eclipse users, then you’ll be extra warm and fuzzy as that’s the IDE
we’ll be using throughout the examples in the book. However, if you are one of those stubborn
types and refuse to be assimilated, don’t panic! Things will still make sense and you’ll be able to
follow right along. Eclipse is mostly harmless and at the end of the day, it’s just Java, right?
If you haven’t figured out it by now, you can download Eclipse from http://eclipse.org. You can
choose any of the 3.X builds and you should be fine. For any and all Eclipse documentation,
check this site: http://www.eclipse.org/eclipse/index.html. You should find everything you need
to help you get started with Eclipse there.
Configuring Quartz within Eclipse
For the examples throughout this book, we’ll create an Eclipse Java project and each chapter will
contain a separate source directory. Figure 3-1 illustrates the Quartz Java project in Eclipse.
***Insert Figure 3.1
03fig03.tif
Figure 3.1
Creating a Quartz Java Project in Eclipse
You will need to import several JARs into the project to build the projects successfully. For one,
you will need the quartz.jar. For now, you can safely pull in the commons-logging.jar,
commons-digester.jar and you should be fine for this chapter. Table 3.1 at the end of the chapter
provides more information on the dependent jars for Quartz.
It would also be a good idea if we pull the Quartz source code into eclipse. This will do two
things for us. First, we’ll be able to set breakpoints and step from our code into Quartz source.
The second benefit is that it will allow you to learn the framework from the inside out. If you
need to see how something is or isn’t working, you’ll have the actual code right there are your
fingertips. “Try that with commercial software!”
Difference between a Quartz Application and a Job
It’s worth pausing for a moment here for a small diversion to explain a
confusing point for some. We’ll be using the phase “Quartz application” to
refer to any software application that uses the Quartz framework as one
of its included libraries. There may be (and usually is) many other
libraries within the application, but if Quartz is present, we will consider it
a Quartz application for the purposes of the discussions throughout the
book. On the other hand, when we talk about a Quartz Job, we’re actually
talking about a specific Java class that performs some task. There are
normally many different types of Jobs within a Quartz application, each
one with a concrete Java class. The two are not used interchangeably.
Chapter 3, 2
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
Creating the Quartz Job Class
Every Quartz job must have a concrete Java class that implements the org.quartz.Job
interface. The Quartz Job interface has a single method execute() that you must implement in
your Job. The execute() method signature is shown here:
public void execute(JobExecutionContext context) throws
JobExecutionException;
When the Quartz scheduler determines that it’s time to fire your job, it instantiates your Job class
and invokes the execute() method. The execute() method is called by the Scheduler with
no expectations other than you throw a org.quartz.JobExecutionException in the event
there’s a problem with the Job. Therefore you are free to perform whatever business logic you
need within the execute() method. A few examples may help to enlighten this point. You are
free to instantiate and call a method on another Class, you can send an email, ftp a file, invoke a
Web Service, call an EJB, execute a workflow, or in the case of our example, check to see if files
exist in a particular directory.
Listing 3.1 below shows our first Quartz Job which is designed to scan a directory for files and
display details about the files.
Listing 3.1
The Example ScanDirectoryJob
package org.cavaness.quartzbook.chapter3;
import java.io.File;
import java.util.Date;
import
import
import
import
import
import
import
org.apache.commons.logging.Log;
org.apache.commons.logging.LogFactory;
org.quartz.Job;
org.quartz.JobDataMap;
org.quartz.JobDetail;
org.quartz.JobExecutionContext;
org.quartz.JobExecutionException;
public class ScanDirectoryJob implements Job {
static Log logger = LogFactory.getLog(ScanDirectoryJob.class);
public void execute(JobExecutionContext context)
throws JobExecutionException {
// Every job has it's own job detail
JobDetail jobDetail = context.getJobDetail();
// The name is defined in the job definition
String jobName = jobDetail.getName();
// Log the time the job started
logger.info(jobName + " fired at " + new Date());
// The diretory to scan is stored in the job map
JobDataMap dataMap = jobDetail.getJobDataMap();
Chapter 3, 3
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
String directoryName = dataMap.getString("SCAN_DIR");
// Validate the required input
if (directoryName == null) {
throw new JobExecutionException(
"Directory not configured");
}
// Make sure the diretory exists
File dir = new File(directoryName);
if (!dir.exists()) {
throw new JobExecutionException(
"Invalid Dir " + directoryName);
}
// Use FileFilter to get only XML files
File[] files = dir.listFiles(new XMLFileOnlyFileFilter());
if (files == null && files.length <= 0) {
logger.info("No XML files found in " + dir);
// Return since there were no files
return;
}
// The number of XML files
int size = files.length;
// Iterate through the files found
for (int i = 0; i < size; i++) {
File file = files[i];
/*
* Log something interesting about each file.
* You can imagine how we could do many things
* with this file, like email, ftp or parse and
* store into db.
*/
String msg =
file.getAbsoluteFile() + " - Size: " + file.length();
logger.info(msg);
}
}
}
Let’s take a closer look at what’s going on in Listing 3.1
When Quartz calls your execute() method, it passes it a
org.quartz.JobExecutionContext that wraps many things about the Quartz runtime
environment and currently executing Job. From the JobExecutionContext, you can access
information about the Scheduler, the Job, the trigger information for the job and much, much
more. In Listing 3.1, the JobExecutionContext is used to access the
org.quartz.JobDetail. The JobDetail class is a wrapper around an instance of your
concrete Job. It holds a reference to the Job instance, as well as other information such as the
Chapter 3, 4
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
name given to the Job instance, the group that the Job belongs to, whether or not the Job is
persistent (volatility) and many other interesting properties.
The JobDetail has an association to the org.quartz.JobDataMap, which wraps a
java.util.Map and holds user-defined properties configured for the particular job. For
example, in Listing 3.1, we get the name of the directory to scan from the JobDataMap. We
could have hard-coded the directory in the ScanDirectoryJob, but we would have had a tough
time reusing the Job for other directories. Later in the section “Scheduling a Quartz Job
Programmatically”, you’ll see exactly how the directory is configured in the JobDataMap.
The rest of the code in the execute() method is just standard Java. It gets the directory name
and creates a java.io.File object. It does a little bit of validation on the directory name to
make sure it’s a valid directory and that it exists. It then calls the listFiles() method on the
File object to retrieve the files from the directory. There is a java.io.FileFilter created
and passed in as an argument to the listFiles() method. The
org.quartzbook.cavaness.XMLFileOnlyFileFilter implements the
java.io.FileFilter interface in order to weed out directories and only return XML files. By
default, the listFiles() method returns everything it finds, whether it’s a file or a subdirectory, so the list must be filtered since we are only interested in XML files.
Note
The XMLFileOnlyFileFilter is not part of the Quartz framework. It’s
a subclass of the java.io.FileFilter, which is part of core Java.
We have created the XMLFileOnlyFileFilter as part of our example
to filter out everything but XML files. It’s quite handy and you should think
about building a set of file filters for your application and reusing them for
your various Quartz jobs.
The XMLFileOnlyFileFilter is shown in Listing 3.2.
Listing 3.2
The XMLFileOnlyFileFilter used in ScanDirectoryJob.java
package org.cavaness.quartzbook.chapter3;
import java.io.File;
import java.io.FileFilter;
public class XMLFileOnlyFileFilter implements FileFilter {
/*
* Pass the File if it's an XML file.
*/
public boolean accept(File file) {
// Lowercase the filename for easier comparison
String lCaseFilename = file.getName().toLowerCase();
return (file.isFile() &&
(lCaseFilename.indexOf(".xml") > 0)) ? true : false;
}
}
Chapter 3, 5
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
The XMLFileOnlyFileFilter is used to block any file (or sub-directories) that doesn’t
contain the string “.xml” as part of its name. This is not perfect, we could actually inspect the
internals of the file to make sure that it’s XML, but the XMLFileOnlyFileFilter will be fine
for this example. Using file filters this way is a very convenient way of selectively choosing
inputs to your Quartz jobs when they involve files as input.
Declarative versus Programmatic Configuration
With Quartz, there are two approaches to configuring the runtime aspects
of an application: 1) Declarative and 2) Programmatic. For some aspects
of the framework, it only makes sense to use external configuration files,
as we all have learned that hard-coding settings within our software has
its limitations.
For other aspects of your Quartz application, you will need to make some
decisions based on the requirements and functionality you need from
Quartz. As you’ll see in the next section, the places where a declarative
or a programmatic decision needs to be made will be highlighted for you.
As much of the Java industry is moving towards a declarative approach,
this will be our preference.
In the next section, we’ll discuss how to configure the Job for scheduling and how to run the
ScanDirectoryJob.
Scheduling the Quartz ScanDirectoryJob
So far, we’ve created a Quartz job but we haven’t discussed what to do with it. We obviously
need a way to set a schedule for the job to run. The schedule may be a one time event or we
might need the job to run every night at midnight, except on Sunday. As the previous chapter
highlighted, the Quartz Scheduler is the heart and soul of the framework. All jobs are registered
with the scheduler and when necessary, the scheduler 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 each and
every execution. This means that any state that you have in instance
variables is lost between job executions. There is a way to persist job
state from one invocation to another, but it’s through the use of the
JobDataMap, which we’ll talk about later in this chapter. When creating a
stateful job, there are a few issues that you must understand. Most
importantly is that no two jobs of the same type can execute concurrently.
So you want to be careful when choosing jobs to be stateful. We’ll
discuss these issues in more depth in Chapter X.
Chapter 3, 6
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
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 necessary steps
to create a Scheduler and start it running.
Listing 3.3
Running a Simplified Quartz Scheduler
package org.cavaness.quartzbook.chapter3;
import java.util.Date;
import
import
import
import
import
org.apache.commons.logging.Log;
org.apache.commons.logging.LogFactory;
org.quartz.Scheduler;
org.quartz.SchedulerException;
org.quartz.impl.StdSchedulerFactory;
public class RunSimpleScheduler {
// Commons Logger
static Log logger = LogFactory.getLog(RunSimpleScheduler.class);
public static void main(String[] args) {
RunSimpleScheduler simple = new RunSimpleScheduler();
simple.runSimpleScheduler();
}
public void runSimpleScheduler() {
Scheduler scheduler = null;
try {
// Get a Scheduler instance from the Factory
scheduler = StdSchedulerFactory.getDefaultScheduler();
// Start the scheduler
scheduler.start();
logger.info("SimpleScheduler was started…” );
logger.info( 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] (RunSimpleScheduler.java:29) - SimpleScheduler was started
INFO [main] (RunSimpleScheduler.java:29) - Thu Jun 09 11:36:48 EDT 2005
Chapter 3, 7
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
Turning off Quartz Info Log Messages
If you are using the Commons Logging framework along with Log4J as
the examples throughout this book are, you may 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 and once you
understand what Quartz is doing, the messages that you may care more
about, can get lost in the volumes of log messages. You can do this by
setting the default logger to log only errors and setting the messages that
come from the example packages to show info. Here’s an example
log4j.properties file that will accomplish this:
# 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.PatternL
ayout
# Pattern output the caller's filename and line #
log4j.appender.stdout.layout.ConversionPattern=%5p
[%t] (%F:%L) - %m%n
# Print only messages of level INFO or above
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 be seen. This is due to the last line in the properties file. If you would
also like to see debug messages from the quartzbook classes, then
change INFO to DEBUG.
As simply as the code in Listing 3.3 seems, you have a running Quartz scheduler. Once the
scheduler is up and running, there are some things you can do with it or information that you can
obtain from it. For example, you might need to pause the scheduler, change some of the settings
and/or jobs and then restart it. This is illustrated in the modifyScheduler() method fragment
shown in Listing 3.4.
Listing 3.4
Pausing and Restarting the Scheduler
private void modifyScheduler(Scheduler scheduler) {
try {
// pause the scheduler
if (!scheduler.isPaused()) {
// pause the scheduler
scheduler.pause();
Chapter 3, 8
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
}
// Make changes to a job, etc.
// 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 once you have a reference to a
Quartz scheduler, there are some pretty interesting things you can do with it. We will add to our
repertoire throughout the book. The culmination of which exists inside the Quartz Web
Application discussed in Chapter X.
As simple as these examples seem to be, there is a catch. Mainly that we haven’t specified any
jobs to be executed or times for those jobs to execute. While the Quartz Scheduler did start up
and run in Listing 3.4, we didn’t specify any jobs to be executed. And that’s what we’re going to
discuss next.
Scheduling a Quartz Job Programmatically
All jobs that we would like to be executed by Quartz need to be registered with the Scheduler. In
most situations, this should be done prior to the starting of the scheduler. As promised earlier in
this chapter, this is one of those areas of Quartz that can be done in either a declarative or
programmatic manner. First, you’ll see how to do it programmatically and then later in this
chapter we’ll repeat this exercise with the declaratively approach.
For each job that is to be registered with the scheduler, a JobDetail needs to be created and
attached to the scheduler instance. This is shown in Listing 3.5 below.
Listing 3.5
Scheduling a Job Programmatically
private void scheduleJob(Scheduler scheduler) {
try {
// Create a new JobDetail
JobDetail jobDetail = new JobDetail("ScanDirectory",
Scheduler.DEFAULT_GROUP,
org.quartzbook.chapter3.ScanDirectoryJob.class);
// Set the directory to scan in our example
jobDetail
.getJobDataMap()
.put("SCAN_DIR", "c:\\quartz-book\\input");
/*
* Setup a trigger to start firing now, with a null end
* date/time, repeat forever and have 10 secs (10000 ms)
Chapter 3, 9
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
* between each firing.
*/
SimpleTrigger trigger = new SimpleTrigger("scanTrigger",
Scheduler.DEFAULT_GROUP, new Date(), null,
SimpleTrigger.REPEAT_INDEFINITELY, 10000L);
// Associate the trigger with the job in the scheduler
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException ex) {
logger.error(ex);
}
}
The scheduleJob() method in Listing 3.5 is part of our example, not part of Quartz. It’s not a
complete example yet, but just the essentials to show how a Job is setup and registered with the
scheduler. The code in this listing creates a JobDetail object for the Job that we wish to run.
The arguments in the constructor for the JobDetail include a name given to the Job, a logical
group to assign the Job to and the fully-qualified class that implements the org.quartz.Job
interface. There are a few constructors for the JobDetail class:
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. If you add two jobs with the same name and group, the first
job may be overwritten or an ObjectAlreadyExistsException will
be thrown, depending on the settings for the Job Store. This will be
discussed further in the next Chapter.
For more detailed information on the Quartz API, see Appendix A.
As stated earlier in this Chapter, the JobDetail acts as a wrapper 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
for a specific Job instance. In listing 3.5, the name of the directory to scan is stored in the
JobDataMap.
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
now see how it gets set.
The next thing we see in listing 3.5 is the use of the org.quartz.SimpleTrigger class.
Triggers are discussed in much more detail later in the book, but suffice to say that Triggers are
the way that the Quartz scheduler knows when to fire the registered jobs. As you can see from
listing 3.5, a SimpleTrigger (which is a concrete subclass of org.quartz.Trigger) is
created and associated with the JobDetail and registered wit the scheduler instance. There are
several constructors for the SimpleTrigger, depending on your needs:
Chapter 3, 10
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
public SimpleTrigger();
public SimpleTrigger(String name, String group);
public SimpleTrigger(String name, String group, int repeatCount,
long repeatInterval);
public SimpleTrigger(String name, String group, Date startTime);
public SimpleTrigger(String name, String group, Date startTime,
Date endTime, int repeatCount, long repeatInterval);
public SimpleTrigger(String name, String group, String jobName,
String jobGroup, Date startTime, Date endTime, int repeatCount,
long repeatInterval);
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 will need
to be registered with the scheduler via the schedulerJob() method.
SimpleTrigger versus CronTrigger
There are two types of Triggers within Quartz: SimpleTrigger and
CronTrigger. A SimpleTrigger is the simpler of the two and is used
primarily to fire single event Jobs. Those that fire at some given time,
repeat for n number of times with a delay of m between each firing and
then quit. CronTriggers are much more complicated and powerful.
They are based on a Calendar and are used when you need to execute a
Job every Monday, Wednesday and Friday at midnight. This chapter will
only focus on the SimpleTrigger. CronTriggers will be covered
later.
If you are reusing a job Class to run multiple instances of the same Job class, you need to create a
JobDetail for each one. For example, if we wanted to reuse the ScanDirectoryJob to check
two different directories, we would need to create and register two instances of the JobDetail
class. Listing 3.6 shows how this could be done.
Listing 3.6
Creating Multiple Instances of the ScanDirectoryJob
package org.cavaness.quartzbook.chapter3;
import java.util.Date;
import
import
import
import
import
import
import
import
org.apache.commons.logging.Log;
org.apache.commons.logging.LogFactory;
org.quartz.JobDetail;
org.quartz.Scheduler;
org.quartz.SchedulerException;
org.quartz.SimpleTrigger;
org.quartz.Trigger;
org.quartz.impl.StdSchedulerFactory;
public class RunMultipleScanDirectoryJobs {
Chapter 3, 11
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
static Log logger =
LogFactory.getLog(RunMultipleScanDirectoryJobs.class);
public static void main(String[] args) {
RunMultipleScanDirectoryJobs simple = new
RunMultipleScanDirectoryJobs();
simple.runScheduler();
}
public void runScheduler() {
Scheduler scheduler = null;
try {
// Get a Scheduler instance from the Factory
scheduler = StdSchedulerFactory.getDefaultScheduler();
/*
* Setup a trigger to start firing now, with a null
end date/time,
* repeat forever and have 10 secs (10000 ms) between
each firing.
*/
SimpleTrigger trigger1 = new
SimpleTrigger("scanTrigger1",
Scheduler.DEFAULT_GROUP, new Date(), null,
SimpleTrigger.REPEAT_INDEFINITELY,
10000L);
// Register first instance of ScanDirectoryJob
scheduleScanDirectoryJob(scheduler, "ScanDirectory",
"c:\\quartz-book\\input1", trigger1);
// Create another instance of a trigger that fires
every 15 secs
SimpleTrigger trigger2 = new
SimpleTrigger("scanTrigger2",
Scheduler.DEFAULT_GROUP, new Date(), null,
SimpleTrigger.REPEAT_INDEFINITELY,
15000L);
// Register another instance of ScanDirectoryJob
scheduleScanDirectoryJob(scheduler, "ScanDirectory2",
"c:\\quartz-book\\input2", trigger2);
// Start the scheduler
scheduler.start();
logger.info("Scheduler was started at " + new Date());
} catch (SchedulerException ex) {
// deal with any exceptions
logger.error(ex);
}
}
private void scheduleScanDirectoryJob(Scheduler scheduler, String
jobName,
String scanDir, Trigger trigger) {
try {
Chapter 3, 12
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
JobDetail jobDetail = new JobDetail(jobName,
Scheduler.DEFAULT_GROUP,
org.cavaness.quartzbook.chapter3.ScanDirectoryJob.class);
// Add the directory to scan
jobDetail.getJobDataMap().put("SCAN_DIR", scanDir);
// Associate the trigger with the job in the scheduler
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException ex) {
logger.error(ex);
}
}
}
Listing 3.6 is pretty straightforward. The runScheduler() method creates a Trigger and
invokes the scheduleScanDirectoryJob() method. The scheduleScanDirectoryJob()
method creates a new JobDetail each time it is invoked, sets the scan directory and registers
the JobDetail and Trigger with the scheduler instance.
Note
We could have passed in enough information to the
scheduleScanDirectory() method so that it could create the
Triggers. This would have made the runScheduler() method a little
cleaner and isolated our dependency on Quartz to the single method.
Running the RunMultipleScanDirectoryJobs Quartz Jobs
If we execute the RunMultipleScanDirectoryJobs class, we should get output similar to the
following:
INFO [main] (RunMultipleScanDirectoryJobs.java:53) - Scheduler was started at Thu Jun 09 12:34:28 EDT 2005
INFO [DefaultQuartzScheduler_Worker-0] (ScanDirectoryJob.java:46) - ScanDirectory1 fired at Thu Jun 09 12:34:28
EDT 2005
INFO [DefaultQuartzScheduler_Worker-8] (ScanDirectoryJob.java:46) - ScanDirectory2 fired at Thu Jun 09 12:34:28
EDT 2005
INFO [DefaultQuartzScheduler_Worker-9] (ScanDirectoryJob.java:46) - ScanDirectory1 fired at Thu Jun 09 12:34:38
EDT 2005
INFO [DefaultQuartzScheduler_Worker-0] (ScanDirectoryJob.java:46) - ScanDirectory2 fired at Thu Jun 09 12:34:43
EDT 2005
Chapter 3, 13
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
Scheduling a Quartz Job Declaratively
As discussed earlier, we would like to handle configuring our software declaratively, rather than
programmatically, as much as possible. If we look back at Listing 3.6, if we needed to change the
time that the Jobs started, or the frequency, we will have to modify source and recompile. This is
fine for these small example applications, but once you have a system that is large and complex,
this quickly become a problem. So if there’s a way to schedule our Quartz jobs declaratively and
our requirements allow for it, you should choose that approach.
Before we can discuss how to schedule your jobs declaratively, we need to talk about the
quartz.properties file. This properties file will allow us to configure the runtime
environment of Quartz (without the file, defaults are used) and more importantly, tell Quartz to
get the job schedules from an external resource.
Configuring the quartz.properties File
The quartz.properties file defines the runtime behavior of the Quartz application and
contains many settings that can be set to control how Quartz behaves. Only the basics will be
covered within this chapter with the more advanced settings saved for later. We will also not go
in detail about the valid values for each setting, saving that for later as well.
Note
The Quartz framework sets defaults for almost all of these properties.
Chapter X will provide more detail.
Let’s look at a bare-bones quartz.properties file and discuss some of the settings. Listing
3.7 shows a very stripped down version of the quartz.properties file.
Listing 3.7
Basic Quartz Properties File
#===============================================================
# Configure Main Scheduler Properties
#===============================================================
org.quartz.scheduler.instanceName = QuartzScheduler
org.quartz.scheduler.instanceId = AUTO
#===============================================================
# Configure ThreadPool
#===============================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 5
org.quartz.threadPool.threadPriority = 5
#===============================================================
# Configure JobStore
#===============================================================
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#===============================================================
# Configure Plugins
Chapter 3, 14
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
#===============================================================
org.quartz.plugin.jobInitializer.class =
org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.validating=false
In the quartz.properties file listed in Listing 7.3, the properties are arbitrarily separated into
four sections. This is a logical separation for the sake of this discussion, the properties don’t have
to be grouped or listed in any order. And obviously the lines with the # are comments.
Note
This discussion does not list every possible setting. We are only going to
discuss here the basic settings that you need to be familiar or that are
necessary to get our declarative example working. To see a complete
listing of settings and their possible values, see Chapter X.
Scheduler Properties
The first section contains two lines and set the name and id for the Scheduler. For most
applications, these settings and their associated values don’t matter. That is, they don’t matter to
your application, but they are set in Quartz and used by the framework. Setting the instanceId
value to AUTO allows Quartz to automatically generate id’s for each Scheduler instance.
ThreadPool Properties
The next section sets the necessary values for the Threads that run in the background and do the
heavy lifting in Quartz. The threadCount property control how many worker threads are
created and available to process jobs. In principal, the more jobs that you have to process, the
more worker threads you’ll need. The number for the threadCount must be at least one but has
a maximum of 200.
The threadPriority property sets the priority that the worker threads run with. Threads with
higher priorities are typically given preference over threads with a lower priority. The minimum
value for the threadPriority is the constant Thread.MAX_PRIORITY, which equates to 10.
The minimum value is Thread.MIN_PRIORITY which equals 1. The typical value for this
property is Thread.NORM_PRIORITY which is 5. For most situations, you’ll want to set this to
the Thread.NORM_PRIORITY value.
JobStore Settings
The properties within the JobStore section describe how the job and trigger information is stored
during the lifecycle of the process. We haven’t really talked about the JobStore and its purpose.
We will save that for later as it’s not necessary for our current example. For now, all you need to
know is that we are storing our Job information in memory – this is opposed to a database.
Storing our Job information in memory is fast and the easiest to configure, but when the
Scheduler process is halted, all Job and trigger state is lost. Job storage in memory is
accomplished by setting the org.quartz.jobStore.class property to
org.quartz.simpl.RAMJobStore as we’ve done in Listing 3.7. If we don’t want to lose our
job state if the JVM is halted, we could use a relational database to store that information. This
Chapter 3, 15
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
requires a different JobStore implementation that we will discuss later. Chapter X will talk about
the various types of JobStores and when you should be using one over the other.
Plugin Settings
The final section in our simple quartz.properties file is the one that specifies any Quartz
plugins that we want to configure. A plugin is an approach used by many other open source
frameworks such as Struts from Jakarta (http://struts.apache.org).
The idea is to declaratively extend the framework by adding Classes that implement the
org.quartz.spi.SchedulerPlugin interface. The SchedulerPlugin interface has three
methods that get called by the Scheduler on startup and shutdown.
Plugins for Quartz are discussed in detail in Chapter X. To declaratively configure our Job
information in our example, we will be using a plugin called
org.quartz.plugins.xml.JobInitializationPlugin that comes with Quartz.
Be default, this plugin searches for a file called quartz_jobs.xml in the classpsath and loads
job and trigger information from it. If we didn’t provide this job information in our source and
had not installed the plugin, the Scheduler would start but not have any jobs to execute, as we
saw back in Listing 3.3.
The next section discusses the quartz_jobs.xml file, which we’ll informally refer to as the
Job definition file.
Note
By default, the JobInitializationPlugin looks for a file called
quartz_jobs.xml on your classpath. You can override this and force it
to look for and use a file with a different name. In order to do this, you will
need to set the filename in the quartz.properties file that we
discussed in the previous section. For now, are going to just rely on the
default filename quartz_jobs.xml and show you how to modify the
quartz.properties file later in this chapter.
Using the quartz_jobs.xml File
Listing 3.8 shows the Job Definition XML file for our Scan Directory example. It configures job
and trigger information using a declaratively approach exactly like Listing 3.5 programmatically.
Let’s display the listing and we’ll walk through it briefly after you have a chance to look at it.
Listing 3.8
The quartz_ jobs.xml file for the ScanDirectory Job
<?xml version='1.0' encoding='utf-8'?>
<quartz xmlns="http://www.quartzscheduler.org/ns/quartz"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.quartzscheduler.org/ns/quartz
http://www.quartzscheduler.org/ns/quartz/job_scheduling_data_1_1.xsd"
version="1.1">
Chapter 3, 16
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
<calendar class-name="org.quartz.impl.calendar.HolidayCalendar"
replace="true">
<name>holidayCalendar</name>
<description>HolidayCalendar</description>
<base-calendar classname="org.quartz.impl.calendar.WeeklyCalendar">
<name>weeklyCalendar</name>
<description>WeeklyCalendar</description>
<base-calendar classname="org.quartz.impl.calendar.AnnualCalendar">
<name>annualCalendar</name>
<description>AnnualCalendar</description>
</base-calendar>
</base-calendar>
</calendar>
<job>
<job-detail>
<name>ScanDirectory</name>
<group>DEFAULT</group>
<description>A job that scans a directory for files</description>
<job-class>
org.cavaness.quartzbook.chapter3.ScanDirectoryJob
</job-class>
<volatility>false</volatility>
<durability>false</durability>
<recover>false</recover>
<job-data-map allows-transient-data="true">
<entry>
<key>SCAN_DIR</key>
<value>c:\quartz-book\input</value>
</entry>
</job-data-map>
</job-detail>
<trigger>
<simple>
<name>scanTrigger</name>
<group>DEFAULT</group>
<job-name>ScanDirectory</job-name>
<job-group>DEFAULT</job-group>
<start-time>2005-06-10 6:10:00 PM</start-time>
<!-- repeat indefinitely every 10 seconds -->
<repeat-count>-1</repeat-count>
<repeat-interval>10000</repeat-interval>
</simple>
</trigger>
</job>
</quartz>
There are two top-level elements within the job definition file in Listing 3.8: the <calendar>
and the <job> elements.
Note
Chapter 3, 17
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
We won’t go into all of the details for the job definition file in this chapter.
For detailed information, see the Plugin Chapter X.
Quartz utilizes the <calendar> element to restrict or eliminate certain dates and/or times from
the Job’s execution. Chapter X will discuss using calendars in detail. For our example here, we
are going to stick with a standard calendar. The <job> element represents a job that you want to
register with the scheduler, just as we did earlier in the chapter with the scheduleJob() method.
You can see the <job-detail> and <trigger> elements, which we programmatically passed
into the schedulerJob() method in Listing 3.5. This is essentially what is happening here, just
in a declarative fashion. You can also see in Listing 3.8 that we set the SCAN_DIR property as
we also did in the Listing 3.5 example.
The <trigger> element is also very intuitive. It simply sets up a SimpleTrigger with the
same properties as before. So, Listing 3.8 is just a different (arguably better) way of doing what
we did in Listing 3.5. You obviously can support multiple jobs as well. We did this in Listing 3.6
programmatically and we can also support it declaratively as we do here in Listing 3.9.
Listing 3.9
Using Multiple Jobs in the quartz_jobs.xml File
<?xml version='1.0' encoding='utf-8'?>
<quartz xmlns="http://www.quartzscheduler.org/ns/quartz"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.quartzscheduler.org/ns/quartz
http://www.quartzscheduler.org/ns/quartz/job_scheduling_data_1_1.xsd"
version="1.1">
<calendar class-name="org.quartz.impl.calendar.HolidayCalendar"
replace="true">
<name>holidayCalendar</name>
<description>HolidayCalendar</description>
<base-calendar classname="org.quartz.impl.calendar.WeeklyCalendar">
<name>weeklyCalendar</name>
<description>WeeklyCalendar</description>
<base-calendar classname="org.quartz.impl.calendar.AnnualCalendar">
<name>annualCalendar</name>
<description>AnnualCalendar</description>
</base-calendar>
</base-calendar>
</calendar>
<job>
<job-detail>
<name>ScanDirectory1</name>
<group>DEFAULT</group>
<description>A job that scans a directory for files</description>
<job-class>
org.cavaness.quartzbook.chapter3.ScanDirectoryJob
Chapter 3, 18
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
</job-class>
<volatility>false</volatility>
<durability>false</durability>
<recover>false</recover>
<job-data-map allows-transient-data="true">
<entry>
<key>SCAN_DIR</key>
<value>c:\quartz-book\input1</value>
</entry>
</job-data-map>
</job-detail>
<trigger>
<simple>
<name>scanTrigger1</name>
<group>DEFAULT</group>
<job-name>ScanDirectory1</job-name>
<job-group>DEFAULT</job-group>
<start-time>2005-06-10 6:10:00 PM</start-time>
<!-- repeat indefinitely every 10 seconds -->
<repeat-count>-1</repeat-count>
<repeat-interval>10000</repeat-interval>
</simple>
</trigger>
</job>
<job>
<job-detail>
<name>ScanDirectory2</name>
<group>DEFAULT</group>
<description>A job that scans a directory for files</description>
<job-class>
org.cavaness.quartzbook.chapter3.ScanDirectoryJob
</job-class>
<volatility>false</volatility>
<durability>false</durability>
<recover>false</recover>
<job-data-map allows-transient-data="true">
<entry>
<key>SCAN_DIR</key>
<value>c:\quartz-book\input2</value>
</entry>
</job-data-map>
</job-detail>
<trigger>
<simple>
<name>scanTrigger2</name>
<group>DEFAULT</group>
<job-name>ScanDirectory2</job-name>
<job-group>DEFAULT</job-group>
<start-time>2005-06-10 6:10:00 PM</start-time>
<!-- repeat indefinitely every 15 seconds -->
<repeat-count>-1</repeat-count>
<repeat-interval>15000</repeat-interval>
</simple>
</trigger>
Chapter 3, 19
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
</job>
</quartz>
Running the Quartz scheduler using this modified quartz_jobs.xml file produces the following
output:
INFO [QuartzScheduler_Worker-0] (ScanDirectoryJob.java:46) - ScanDirectory1 fired at Fri Jun 10 19:22:50 EDT 2005
INFO [QuartzScheduler_Worker-1] (ScanDirectoryJob.java:46) - ScanDirectory1 fired at Fri Jun 10 19:23:00 EDT 2005
INFO [QuartzScheduler_Worker-1] (ScanDirectoryJob.java:46) - ScanDirectory2 fired at Fri Jun 10 19:23:00 EDT 2005
INFO [QuartzScheduler_Worker-2] (ScanDirectoryJob.java:46) - ScanDirectory1 fired at Fri Jun 10 19:23:10 EDT 2005
INFO [QuartzScheduler_Worker-3] (ScanDirectoryJob.java:46) - ScanDirectory2 fired at Fri Jun 10 19:23:15 EDT 2005
INFO [QuartzScheduler_Worker-4] (ScanDirectoryJob.java:46) - ScanDirectory1 fired at Fri Jun 10 19:23:20 EDT 2005
You can see that the ScanDirectory1 job is executed every 10 seconds while ScanDirectory2
does indeed fire every 15 seconds, just as in Listing 3.6
Modifying the quartz.properties File for the Plugin
Earlier in this chapter, you were told that the JobInitializationPlugin looks for the file
quartz_jobs.xml to get the declarative job information from. If you would like to change that file,
you will need to modify the quartz.properties file and tell the plugin which file to load. Listing
3.10 shows how this can be accomplished. We are only repeating the Plugin section here.
Listing 3.10
Modifying quartz.properties for JobInitializationPlugin
org.quartz.plugin.jobInitializer.class =
org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.fileName = my_quartz_jobs.xml
org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
org.quartz.plugin.jobInitializer.validating = false
org.quartz.plugin.jobInitializer.overWriteExistingJobs = false
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
In Listing 3.10, we add the property org.quartz.plugin.jobInitializer.fileName and
set the value to the name of our file. It must be available to the classloader, which means
somewhere on the classpath.
When Quartz starts up and reads the quartz.properties file, it will initialize the plugin. It
passes all of these properties to the plugin and the plugin gets notified to look for a different file.
Chapter 3, 20
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
Packaging the Quartz Application
Let’s finish off this early chapter and briefly discuss the process of packaging an application that
utilizes the Quartz framework.
Quartz Third-Party Dependencies
If you look back at your Quartz distribution, you will see a <quartz_home>\lib directory. In
this directory, you’ll find the quartz.jar and several third-party libraries. For deployment, you
will absolutely need the quartz.jar as well as some of the other dependent libraries. Which thirdparty libraries are required is somewhat dependent on whether you are running in a standalone
environment or as part of a J2EE distribution. Typically the commons libraries (commonslogging, commons-beanutils, etc) are needed regardless. However, when deploying into an
application server environment, you need to make sure not to copy JARs that are already present,
as you will often get very strange results and we’re not referring to strange as in a “good” way.
Table 3.1 lists the included third-part libraries and information to help you decide if you need to
include it.
Table 3.1
Quartz Third-Party Libraries Required/Optional
Name
Required
Notes and URL
Activation.jar
http://java.sun.com/products/javabeans/glasgow/jaf.html
Commons-beanutils.jar
http://jakarta.apache.org/commons/beanutils/
Commons-collections.jar
http://jakarta.apache.org/commons/collections/
commons-dbcp-1.1.jar
http://jakarta.apache.org/commons/dbcp/
commons-digester.jar
If you are using some of the Plugins, you’ll need it
commons-logging.jar
Yes
http://jakarta.apache.org/commons/logging/
commons-pool-1.1.jar
http://jakarta.apache.org/commons/pool/
javamail.jar
http://java.sun.com/products/javamail/
jdbc2_0-stdext.jar
http://java.sun.com/products/jdbc/
jta.jar
http://java.sun.com/products/jta/
quartz.jar
Yes
Core Quartz Framework
servlet.jar
http://java.sun.com/products/servlet/
Log4j.jar
But whose not using this, come on.
Configuration and Properties Files
It will also be necessary to include the quartz.properties with your application. If you are
deploying your application in an exploded format,1 then you should place the
An “exploded” format is one that is not contained within a JAR, WAR, EAR or other Java archive, but that
is laid on the file system in individual files.
1
Chapter 3, 21
Draft Chapter from upcoming Quartz Book by Chuck Cavaness
The track changes feature is turned on so feel free to type your comments, changes or
suggestions directly into this document and send the document back to
chuckcavaness@yahoo.com
quartz.properties file in a directory that is loaded by the classloader. For example, if you
have a classes directory, then store the quartz.properties file there. If you have are
deploying in one of Java’s Archived formats, then put the properties file in the root of the
Archive file. This same rule goes when using the quartz_jobs.xml file as well.
Chapter 3, 22
Download