As discussed earlier, we would like to handle configuring our software declaratively rather than programmatically as much as possible. Looking back at Listing 3.6, if we needed to change the time or frequency at which the jobs started, we would have to modify the source and recompile. This is fine for small example applications, but with a system that is large and complex, this quickly becomes a problem. So if there's a way to schedule Quartz jobs declaratively and your requirements allow for it, you should choose that approach every time.
Before we can discuss how to schedule your jobs declaratively, we need to talk about the quartz.properties file. This properties file enables you to configure the runtime environment of Quartz and, more important, tell Quartz to get the job and trigger information from an external resource, such as an XML file.
Configuring the quartz.properties File
The quartz.properties file defines the runtime behavior of the Quartz application and contains many properties that can be set to control how Quartz behaves. Only the basics are covered within this chapter; we save the more advanced settings for later. We also do not go into detail about the valid values for each setting at this point.
Let's look at a bare-bones quartz.properties file and discuss some of the settings. Listing 3.7 shows a stripped-down version of the quartz.properties file.
The Quartz framework sets defaults for almost all of these properties.
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.threadCount = 5 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool #=============================================================== #Configure JobStore #=============================================================== org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore #=============================================================== #Configure Plugins #=============================================================== 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 in Listing 3.7, the properties are logically separated into four sections. The properties don't have to be grouped or listed in any order. The lines with the # are comments.
This discussion does not list every possible setting. Here we discuss only the basic settings that you need to be familiar or that are necessary to get the declarative example working. These properties are discussed throughout the book in the chapters that pertain to the relevant sections of the properties file.
The first section contains two lines and sets the instanceName and instanceId for the Scheduler. The value for the property org.quartz.scheduler.instanceName can be any string that you like. It's used to distinguish a particular scheduler instance when multiple schedulers are being used. Multiple schedulers are normally used within a clustered environment. (Quartz clustering is discussed in Chapter 11, "Clustering Quartz.") For now, a string such as this one is fine:
org.quartz.scheduler.instanceName = QuartzScheduler
In fact, that is the default when none is provided in the properties file.
The second Scheduler property shown in Listing 3.7 is org.quartz.scheduler.instanceId. As with the instanceName property, the instanceId property can be any string value you want. The value should be unique across all Scheduler instances, especially within a cluster. You may use the value AUTO if you want the value to be generated for you. The generated value will be NON_CLUSTERED if this is running in a nonclustered Quartz environment. If you're using Quartz in a clustered environment, the value will be the hostname of the machine plus the current date and time. For most situations, setting the value to AUTO is fine.
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 controls 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 1. Quartz imposes no maximum on the number of worker threads, but setting this value to greater than 100 on most machines becomes quite unwieldy, especially if your jobs have long-running logic in them. There is no default, so you must specify a value for this property.
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 java.lang.Thread.MAX_PRIORITY, which equates to 10. The minimum value is java.lang.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 value of 5, which is the default if the property isn't specified.
The final threadpool setting is for the property org.quartz.threadPool.class. The value for this class should be a fully qualified name of the class that implements the org.quartz.spi.ThreadPool interface. The threadpool that ships with Quartz is org.quartz.simpl.SimpleThreadPool, and it should meet the needs of most users. This threadpool has simple behavior and is well tested. It provides a fixed-size pool of threads that survive the lifetime of the Scheduler. You can create your own threadpool class, if you want. For example, you might need to do this if you want a threadpool that grows and shrinks with demand. There is no default specified, so you must provide a value for this property.
The properties within the JobStore section describe how job and trigger information is stored during the lifetime of the Scheduler instance. We haven't yet talked about the JobStore and its purpose; we save that for later because it's not necessary for the current example. For now, all you need to know is that we are storing Scheduler information in memory instead of a relational database.
Storing Scheduler information in memory is fast and the easiest to configure. When the Scheduler process is halted, however, 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 Scheduler state when the JVM is halted, we could use a relational database to store that information. This requires a different JobStore implementation that we discuss later. Chapters 5, "Cron Triggers and More," and 6, "JobStores and Persistence," cover the various types of JobStores and when you should use them.
The final section in the simple quartz.properties file is the one that specifies any Quartz plug-ins that you want to configure. A plug-in is used by many other open source frameworks, such as Struts from Apache (see 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 are called by the Scheduler.
Plug-ins for Quartz are discussed in detail in Chapter 8, "Using Quartz Plug-Ins."
To declaratively configure the Scheduler information for our example, we will be using a plug-in called org.quartz.plugins.xml.JobInitializationPlugin that comes with Quartz.
By default, this plug-in searches for a file called quartz_jobs.xml in the classpath and loads job and trigger information from the XML file.
The next section discusses the quartz_jobs.xml file, which we informally refer to as the job definition file.
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. To do this, you must set the filename in the quartz.properties file that we discussed in the previous section. For now, we 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 the Scan Directory example. It configures job and trigger information using a declarative approach exactly like the example from Listing 3.5.
Listing 3.8. The quartz_ jobs.xml file for the ScanDirectory Job
ScanDirectory DEFAULT A job that scans a directory for files org.cavaness.quartzbook.chapter3.ScanDirectoryJob false false false SCAN_DIR c:quartz-bookinput scanTrigger DEFAULT ScanDirectory DEFAULT 2005-06-10 6:10:00 PM -1 10000
The 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 and elements, which we programmatically passed into the schedulerJob() method in Listings 3.5. This is essentially what is happening here, but in a declarative fashion. You can also see in Listing 3.8 that we set the SCAN_DIR property into the JobDataMap, as we also did in the Listing 3.5 example.
The 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. Listing 3.9 shows the comparable version of Listing 3.6.
Listing 3.9. You Can Specify Multiple Jobs in the quartz_jobs.xml File
ScanDirectory1 DEFAULT A job that scans a directory for files org.cavaness.quartzbook.chapter3.ScanDirectoryJob false false false SCAN_DIR c:quartz-bookinput1 scanTrigger1 DEFAULT ScanDirectory1 DEFAULT 2005-07-19 8:31:00 PM -1 10000 ScanDirectory2 DEFAULT A job that scans a directory for files org.cavaness.quartzbook.chapter3.ScanDirectoryJob false false false SCAN_DIR c:quartz-bookinput2 scanTrigger2 DEFAULT ScanDirectory2 DEFAULT 2005-06-10 6:10:00 PM -1 15000
Modifying the quartz.properties File for the Plug-In
Earlier in this chapter, you were told that the JobInitializationPlugin looks for the file quartz_jobs.xml to get the declarative job information. If you want to change that file, you need to modify the quartz.properties file and tell the plug-in which file to load. For example, if you wanted Quartz to load job information from an XML file called my_quartz_jobs.xml, you would have to give the plug-in that filename. Listing 3.10 shows how this can be accomplished; we are only repeating the plug-in 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 initializes the plug-in. It passes all of these properties to the plug-in, and the plug-in gets notified to look for a different file.
Packaging the Quartz Application
Scheduling in the Enterprise
Getting Started with Quartz
Cron Triggers and More
JobStores and Persistence
Implementing Quartz Listeners
Using Quartz Plug-Ins
Using Quartz Remotely
Using Quartz with J2EE
Quartz and Web Applications
Using Quartz with Workflow
Appendix A. Quartz Configuration Reference