The Hello, World Quartz Project

This example application is a little more interesting than the proverbial System.out.println("Hello World from Quartz"). When Quartz executes a job, we expect it to perform some interesting and useful task on our behalf. That's what we are going to do heresomething useful and interesting.

This chapter shows you how to create a Quartz job that, when told to do so by our Quartz application, will scan a specified directory and look for XML files. If it finds one or more XML files within the specified directory, it will print some cursory information about the file. How is this useful and interesting, you say? Hopefully, you can make the leap that after your job detects certain files in a directory, it can do many interesting things with those files. You might want to ftp them to a remote host or e-mail them as attachments. Perhaps the files are orders that were sent from a customer and need to be inserted into your database. Infinite possibilities exist; we discuss some of the more interesting ones later in the book.

All attempts have been made to keep this new material straightforward and to cover only the essentials. However, we also 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; we spend very little time explaining the standard aspects of it.

Finally, this chapter wraps up with a brief discussion of how to package the example application. Apache Ant is our choice for building and packaging Java projects: Quartz applications are no exception.

Setting Up the Quartz Project

The first step is to set up your development environment for this project. You can choose any development tool or IDE that makes you feel warm and fuzzy; Quartz won't hold it against you. If you're part of the growing mass of Java developers that have realized just how great Eclipse is, you'll be extra warm and fuzzy: We use that IDE throughout the examples in the book.

If you haven't already done so, you can download Eclipse from http://eclipse.org. You can choose any of the 3.x. For Eclipse documentation, check www.eclipse.org/eclipse/index.html; you should find everything you need to help you get started with Eclipse.

Configuring Quartz Within Eclipse

For the examples throughout this book, we create an Eclipse Java project; each chapter contains a separate source directory. Figure 3.1 illustrates the Quartz Java project in Eclipse.

Figure 3.1. Creating a Quartz Java project in Eclipse

You need to import several JARs into the project to build them successfully. To start, you need the Quartz binary, which is named quartz-.jar. Quartz also requires several third-party libraries to work; the ones you need depend on which features of the framework you're using. For now, you should include the Commons Logging, Commons Collections, Commons BeanUtils, and Commons Digester libraries, found in the /lib/core and /lib/optional directories. Table 3.1 provides more information on the dependent JARs for Quartz.

It's also a good idea to pull the Quartz source code into Eclipse. This does two things for you. First, it enables you to set breakpoints and step from the code into Quartz source. Second, it helps you learn the framework from the inside out. If you need to see how something is (or isn't) working, you have the actual code right there at your fingertips. Try that with commercial software!

Difference Between A Quartz Application And A Job

It's worth pausing for a moment here to explain a potentially confusing point. We use the phase "Quartz application" to refer to any software application that uses the Quartz framework as one of its included libraries. Many other libraries generally are used within the application, but if Quartz is present, we consider it a Quartz application for the purposes of the discussions throughout this 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. Normally many different types of jobs exist within a Quartz application, each one with a concrete Java class. The two terms are not used interchangeably.

 

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 a job, it instantiates the job class and invokes the execute() method. The Scheduler calls the execute() method with no expectations other than that you throw an org.quartz.JobExecutionException if there's a problem with the job.

You can perform whatever business logic you need within the execute() method: For example, you might instantiate and call a method on another class, send an e-mail, 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 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 org.apache.commons.logging.Log; import Org.apache.commons.logging.LogFactory; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** *

* A simple Quartz job that, once configured, will scan a * directory and print out details about the files found * in the directory. *

* Subdirectories will filtered out by the use of a * {@link FileExtensionFileFilter}. * * @author Chuck Cavaness * @see java.io.FileFilter */ public class ScanDirectoryJob implements Job { static Log logger = LogFactory.getLog(ScanDirectoryJob.class); public void execute(JobExecutionContext context) throws JobExecutionException { // Every job has its 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 directory to scan is stored in the job map JobDataMap dataMap = jobDetail.getJobDataMap(); String dirName = dataMap.getString("SCAN_DIR"); // Validate the required input if (dirName == null) { throw new JobExecutionException( "Directory not configured" ); } // Make sure the directory exists File dir = new File(dirName); if (!dir.exists()) { throw new JobExecutionException( "Invalid Dir "+ dirName); } // Use FileFilter to get only XML files FileFilter filter = new FileExtensionFileFilter(".xml"); File[] files = dir.listFiles(filter); 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. File aFile = file.getAbsoluteFile(); long fileSize = file.length(); String msg = aFile + " - Size: " + fileSize; logger.info(msg); } } }

Let's take a closer look at what's going on in Listing 3.1.

When Quartz calls the execute() method, it passes it an org.quartz.JobExecutionContext, which 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 class. The JobDetail class holds the detailed information about a job, including the name given to the job instance, the group that the job belongs to, whether the Job is persistent (volatility), and many other interesting properties.

The JobDetail has a reference to org.quartz.JobDataMap. The JobDataMap 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. In the later 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. java.io.FileFilter is created and passed in as an argument to the listFiles() method. org.quartzbook.cavaness. FileExtensionFileFilter implements the java.io.FileFilter interface to weed out directories and return only 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 because we are interested only in XML files.

Note

The FileExtensionFileFilter 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 FileExtensionFileFilter as part of our example to filter out everything but XML files. This is quite handyyou should think about building a set of file filters for your application and reusing them for your various Quartz jobs.

Listing 3.2 shows the FileExtensionFileFilter.

Listing 3.2. The FileExtensionFileFilter Used in ScanDirectoryJob.java

package org.cavaness.quartzbook.chapter3; import java.io.File; import java.io.FileFilter; /** * A FileFilter that only passes Files of the specified extension. *

* Directories do not pass the filter. * * @author Chuck Cavaness */ public class FileExtensionFileFilter implements FileFilter { private String extension; public FileExtensionFileFilter(String extension) { this.extension = extension; } /* * Pass the File if it has the extension. */ public boolean accept(File file) { // Lowercase the filename for easier comparison String lCaseFilename = file.getName().toLowerCase(); return (file.isFile() && (lCaseFilename.indexOf(extension) > 0)) ? true:false; } }

The FileExtensionFileFilter is used to block any file that doesn't contain the string ".xml" as part of its name. It also blocks any subdirectories, which would normally be returned as well from the listFiles() method. Using file filters 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: declarative and programmatic. For some aspects of the framework, it makes sense to use external configuration files: We all have learned that hard-coding settings within our software has its limitations.

For other aspects of your Quartz application, you will have to make some decisions based on the requirements and functionality you need from Quartz. The next section highlights when you must use a declarative and a programmatic approach. Because much of the Java industry is moving toward a declarative approach, this is our preference.

In the next section, we discuss how to configure the job for scheduling and how to run the ScanDirectoryJob.

Категории