The Quartz SchedulerFactory
Regardless of the type of Scheduler you use, you should never create an instance of the Scheduler directly. Instead, you should use one of the factory methods to ensure that all facets of the Scheduler are instantiated and initialized properly. (The factory design pattern is referred to as a factory pattern because it is responsible for "manufacturing" an object. In this case, it manufactures a Scheduler instance) The Quartz framework provides the org.quartz.SchedulerFactory interface for this purpose. The role of the SchedulerFactory is to produce Scheduler instances. When created, the instances are stored in a repository (org.quartz.impl.SchedulerRepository) that provides lookup facilities within a class loader. To use the Scheduler instances, the client must retrieve them from the factory (and subsequent repository) by using a separate method call. In other words, creating a Scheduler instance through the factory and getting the instance of that Scheduler takes two method calls. Some convenience methods encapsulate those two calls, as you'll see shortly.
You can use two different types of the SchedulerFactory to create Schedulers (see Figure 4.3).
Figure 4.3. All Scheduler instances should be created by using the SchedulerFactory.
The two Scheduler factories are org.quartz.impl.DirectSchedulerFactory and org.quartz.impl.StdSchedulerFactory. Let's examine each one.
Using the DirectSchedulerFactory
The DirectSchedulerFactory is designed for those who want absolute control over how a Scheduler instance is manufactured. Listing 4.1 shows that the easiest way to use the DirectSchedulerFactory to create a Scheduler instance.
Listing 4.1. Using the DirectSchedulerFactory
public class Listing_4_1 { static Log logger = LogFactory.getLog(Listing_4_1.class); public static void main(String[] args) { Listing_4_1 example = new Listing_4_1(); example.startScheduler(); } public void startScheduler() { DirectSchedulerFactory factory=DirectSchedulerFactory.getInstance(); try { // Initialize the Scheduler Factory with 10 threads factory.createVolatileScheduler(10); // Get a scheduler from the factory Scheduler scheduler = factory.getScheduler(); // Start the scheduler running logger.info("Scheduler starting up..."); scheduler.start(); } catch (SchedulerException ex) { logger.error(ex); } } } |
When using the DirectSchedulerFactory, there are three basic steps. First, you must acquire an instance of the factory using the static getInstance() method. When you have the factory instance, you need to initialize it by calling one of the createXXX methods. For example, Listing 4.1 used the createVolatileScheduler() method to tell the factory to initialize itself with ten worker threads (we talk more about worker threads later in this chapter). The third and final step is to retrieve an instance of a Scheduler from the factory with the getScheduler() method.
You can choose from several different createXXX() methods, depending on what type of Scheduler you want to use and how you need it configured. Listing 4.1 used the createVolatileScheduler() method to create a Scheduler. The createVolatileScheduler() method takes a single argument: the number of threads to create. In Figure 4.2, you saw that there is also a RemoteScheduler class. You must use a different createXXX() method to create a RemoteScheduler instance. Two versions are available:
public void createRemoteScheduler(String rmiHost, int rmiPort) throws SchedulerException; protected void createRemoteScheduler(String schedulerName, String schedulerInstanceId, String rmiHost, int rmiPort) throws SchedulerException;
RemoteSchedulers are discussed in Chapter 10, "Using Quartz with J2EE." If you just need a standard Scheduler, you can use one of these versions:
public void createScheduler(ThreadPool threadPool, JobStore jobStore) throws SchedulerException; public void createScheduler(String schedulerName, String schedulerInstanceId, ThreadPool threadPool, JobStore jobStore) throws SchedulerException; public void createScheduler(String schedulerName, String schedulerInstanceId, ThreadPool threadPool, JobStore jobStore, String rmiRegistryHost, int rmiRegistryPort, long idleWaitTime, long dbFailureRetryInterval) throws SchedulerException;
In the last chapter, we used a properties file to initialize the Scheduler. To create a Scheduler instance with DirectSchedulerFactory, you must pass configuration parameters through one of the createXXX() methods. In the next section, we discuss the StdSchedulerFactory, a version of SchedulerFactory that relies on a set of properties to configure the Scheduler instead of passing them in as arguments to the createXXX method. It thus avoids hard-coding Scheduler configuration settings within your code.
Using the StdSchedulerFactory
In contrast to the DirectSchedulerFactory, the org.quartz.impl.StdSchedulerFactory relies on a set of properties to determine how to manufacture the Scheduler instance. You can supply these properties to the factory in one of three ways:
- By supplying a java.util.Properties instance
- By supplying an external properties file
- By supplying a java.io.InputStream that has the contents of a properties file
Java Properties Files
We use the phase "properties file" here in the traditional Java sense: a set of key=value pairs that are typically specified in an external file as key=value one per line. |
Listing 4.2 demonstrates the first approach, supplying the properties through a java.util.Properties instance.
Listing 4.2. Creating a StdSchedulerFactory Using a java.util.Properties Instance
public class Listing_4_2 { static Log logger = LogFactory.getLog(Listing_4_2.class); public static void main(String[] args) { Listing_4_2 example = new Listing_4_2(); example.startScheduler(); } public void startScheduler() { // Create an instance of the factory StdSchedulerFactory factory = new StdSchedulerFactory(); // Create the properties to configure the factory Properties props = new Properties(); // required to supply threadpool class and num of threads props.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, "org.quartz.simpl.SimpleThreadPool"); props.put("org.quartz.threadPool.threadCount", "10"); try { // Initialize the factory with properties factory.initialize(props); Scheduler scheduler = factory.getScheduler(); logger.info("Scheduler starting up..."); scheduler.start(); } catch (SchedulerException ex) { logger.error(ex); } } } |
Listing 4.2 provides a very simple example of using the StdSchedulerFactory to create an instance of a Scheduler. The two properties passed into the factory in the example are the name of a class that implements the org.quartz.spi.ThreadPool interface and the number of threads that the Scheduler should use to process jobs. These two properties are required because no defaults are set for them (we talk more about these shortly).
With the DirectSchedulerFactory from Listing 4.1, we called one of the createXXX() methods to initialize the factory. With the StdSchedulerFactory, you use one of the available initialize() methods. After the factory has been initialized, you can call the getScheduler() method to acquire the instance of the Scheduler. Passing the properties to the factory using the java.util.Properties object is just one way of configuring the SchedulerFactory. Hard-coding configuration properties is hardly recommended and should be avoided when possible. If you needed to modify the number of threads in this example, you would need to change the code and recompile.
Fortunately, the StdSchedulerFactory provides other ways to supply the necessary properties. The factory can also be initialized by passing in the name of an external file that contains these configuration settings. This alternate form of the initialize() method is shown here:
public void initialize(String filename) throws SchedulerException;
For the file and properties to be loaded successfully, the file must be available to the classloader. This means that it should be in the classpath of your application. If you would rather use an java.io.InputStream to load the file, you can use this alternate form of the initialize() method:
public void initialize(InputStream propertiesStream) throws SchedulerException;
In Chapter 3, "Hello, Quartz," you saw an example that used an external file called quartz.properties to load the settings for the SchedulerFactory. This external properties file is just a special use of the previous method signatures. If you don't specify where to get the properties using one of the initialize() methods, the StdSchedulerFactory attempts to load them from a file called quartz.properties. This is the behavior you saw in Chapter 3.
Creating a Scheduler Using the Default quartz.properties File
If you use the no-argument version of the initialize() method, the StdSchedulerFactory performs the following steps to attempt to load the properties for the factory:
1. |
Checks for a different filename using System.getProperty("org.quartz.properties");
|
2. |
Otherwise, uses the name quartz.properties as the file to load
|
3. |
Tries to load the file from the current working directory
|
4. |
Tries to load the file from the system classpath
|
Using the StdSchedulerFactory for creating a Scheduler instance is so common that the StdSchedulerFactory includes a static convenience method called geTDefaultScheduler() that uses the previous steps outlined to instantiate the factory. This is shown in Listing 4.3.
Listing 4.3. Using the Static getdefaultScheduler() Method to Create a Scheduler
public class Listing_4_3 { static Log logger = LogFactory.getLog(Listing_4_3.class); public static void main(String[] args) { Listing_4_3 example = new Listing_4_3(); example.startScheduler(); } public void startScheduler() { try { // Create a default instance of the Scheduler Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); logger.info("Scheduler starting up..."); scheduler.start(); } catch (SchedulerException ex) { logger.error(ex); } }} |
The static getdefaultScheduler() method calls the empty constructor. If none of the initialize() methods has been previously invoked, the no-argument initialize() method is called. This sets in motion the load file sequence mentioned earlier. In the default case, the quartz.properties file is located, and the properties are loaded from that file.
Scheduler Functionality
Most of the material in this chapter so far has focused on obtaining an instance of the Scheduler. So when you have a Scheduler instance, what can you do with it? Well, to start, the examples have shown that you can call the start() method on it. The Scheduler API includes approximately 65 different methods. We don't enumerate all of them here, but you need to understand a little more about the Scheduler API.
The Scheduler API can be grouped into three categories:
- Managing the Scheduler
- Managing jobs
- Managing triggers and calendars