ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Scheduling Jobs in a Java Web Application

by Chris Hardin
03/01/2006

In large J2EE web applications, a common task is scheduling tasks. Developers want to do all kinds of operations that can run at specified intervals and complete tasks that don't require user input. Java has a myriad of ways to accomplish this task, but there is no standard for doing this in a web application. This can become a great problem if you have many developers working on the same project and each of them decides to implement scheduling in his or her own way. Memory and synchronization issues are the first two things that come to mind when each developer rolls their own solution. In fact, some developers are tempted to make system calls to an operating-system-level scheduling mechanism like cron on Unix platforms. This is not only a bad programming practice, but it throws portability right out of the window.

Why Schedule?

In a web application, most tasks are accomplished in a manner that prevents the user from having to wait too long. Think of a Google search, for example. Keeping the user from waiting is extremely important for the sake of the user experience. One solution for asynchronous tasks is to spawn a thread after a user submits, but that doesn't really solve situations where the task needs to re-occur at specific intervals or run a certain time of the day every day.

Let's take an example of a database report and see how scheduling can help us in our application design. Reports can be intricate and complicated, depending on what kind of data a user wants to see and whether there is a ton of data aggregated into the report from a database or multiple databases. It could take a long time for a user to run an "on demand" report like this. So let's add a scheduling mechanism to our reporting example to allow a user to schedule the report for whenever he or she wants it and have it emailed to them as a PDF or in some other format. The user could schedule the report to run every day at 2:22 a.m., when the system might be in a less stressed state, or they might choose to run it just once, at a specific time. By adding scheduling to our reporting application, we can add a valuable feature and improve the user's experience of the product.

Luckily, there is a robust open source solution that works like a charm to standardize the way you conduct scheduling in a web application (or any other Java application, for that matter). The following examples will show you how to use Quartz, an open source scheduling library, to create a scheduling framework in your web application. The example also uses Struts Action framework plugins in order to initialize the scheduling mechanism when the web application starts. The Struts Action framework (or just "Struts") is a such a common framework that it should be familiar for most developers. Of course, there are many other frameworks available for Java web applications that facilitate a Model-View-Controller design pattern.

Initializing a Scheduler on Startup

The first thing we want to do is set up the Struts plugin to create our scheduler when the container starts. For these examples, it is assumed that Tomcat will be the web application container of choice, but the examples should work in any container. We will create a Struts plugin class and add some lines to the struts-config.xml file to get this going.

The plugin configuration has two properties used for initialization. startOnLoad specifies whether or not we want to start the scheduler immediately when the container starts and startupDelay is used to specify how long before the code will attempt to start the scheduler. The delay is important, because we might want to start the process after more important initialization steps. A listener mechanism could also be used here to provide a more sophisticated mechanism for notifying the SchedulerPlugIn to start the Quartz Scheduler.

  <plug-in className="SchedulerPlugIn">
     <set-property property="startOnLoad" value="false" />
     <set-property property="startupDelay" value="0" />
  </plug-in>

We are going to create a class of our own; the SchedulerPlugIn is merely a singleton class that implements the Struts interface org.apache.struts.action.PlugIn. Struts will instantiate plugins in the order in which they appear in the config file. The lines to pay particular attention to are from the init() method, where we instantiate our Quartz objects and get the Scheduler. The org.quartz.Scheduler object, which we will discuss later, is where we will submit our job information. The Scheduler will be instantiated by the Quartz servlet configuration, much like Struts initializes its ActionServlet class. Let's take a look at the init() method:


public void init(ActionServlet actionServlet,
                 ModuleConfig moduleConfig) {

  System.out.println("Initializing Scheduler PlugIn for Jobs!");
  // Retrieve the ServletContext
  ServletContext ctx = actionServlet.getServletContext();
  // The Quartz Scheduler
  Scheduler scheduler = null;

  // Retrieve the factory from the ServletContext.
  // It will be put there by the Quartz Servlet
  StdSchedulerFactory factory =  (StdSchedulerFactory) 
      ctx.getAttribute(QuartzInitializerServlet.QUARTZ_FACTORY_KEY);
    
  try{
        // Retrieve the scheduler from the factory
        scheduler = factory.getScheduler();

        // Start the scheduler in case, it isn't started yet
        if (m_startOnLoad != null && 
            m_startOnLoad.equals(Boolean.TRUE.toString())){
            System.out.println("Scheduler Will start in " +
            m_startupDelayString + " milliseconds!");
            //wait the specified amount of time before
            // starting the process.
            Thread delayedScheduler =
                new Thread(new DelayedSchedulerStarted (
                                   scheduler, m_startupDelay));
            //give the scheduler a name. All good code needs a name
            delayedScheduler.setName("Delayed_Scheduler");
            //Start out scheduler
            delayedScheduler.start();
        }
  } catch (Exception e){
     e.printStackTrace();
  }
    sm_scheduler = scheduler;
}
Real World Web Services

Related Reading

Real World Web Services
Integrating EBay, Google, Amazon, FedEx and more
By Will Iverson

The second configuration step is to include some lines into the web.xml file to initialize the Quartz servlet org.quartz.ee.servlet.QuartzInitializerServlet, because it will add the SchedulerFactory to the ServletContext so we can access it from our Struts plugin. The SchedulerFactory is where we will get our Scheduler object from the Struts plugin. Aside from the struts-config.xml and web.xml entries, you will need a quartz.properties file located in the classes directory of your web application. It is also possible to pass the location of this file to the QuartzInitializerServlet as a parameter in the web.xml file.

  <servlet>

     <servlet-name>QuartzInitializer</servlet-name>
     <display-name>Quartz Initializer Servlet</display-name>

  <servlet-class>
    org.quartz.ee.servlet.QuartzInitializerServlet
  </servlet-class>
  <load-on-startup>1</load-on-startup>
  <init-param>
    <param-name>shutdown-on-unload</param-name>
    <param-value>true</param-value>
  </init-param>

  <init-param>
    <param-name>start-scheduler-on-load</param-name>
    <param-value>false</param-value>
    </init-param>

 </servlet>

It is possible to completely remove the usage of the SchedulerPlugIn and Struts completely, but the extra layer of abstraction can be useful if you decide to replace Quartz in favor of some other scheduling framework in the future. Keeping everything loosely coupled will make life easier in the long run. If you use another MVC framework, you can use the code from inside of the SchedulerPlugIn.init() method to achieve the same result with another framework. It is also possible to achieve the same initialization through the use of the ServletContextListener introduced in the Servlet 2.3 specification.

Now that we have our web application configured, we can create and deploy a .war file and watch the console log to see the output from the SchedulerPlugIn. Before we do that, though, let's see how we can submit a job to the scheduler.

In any class of our web application, we can refer to the singleton instance SchedulerPlugIn and schedule some work for execution. The first thing we need is a Trigger to tell the job when to run and how often to run. Quartz has support for several types of triggers. For this example, we will use a CronTrigger.

Trigger trigger = new CronTrigger("trigger1", "group1");
trigger.setCronExpression("0 0 15 ? * WED");

The Trigger above will execute the job every Wednesday at 3:00 p.m. Now all we have to do is to create a JobDetail object and pass that and the trigger to the scheduleWork() method of the SchedulerPlugIn.

    JobDetail jobDetail =
        new JobDetail("Hello World Job",
                      "Hello World Group",
                      HelloWorld.class,
                      true, true, true);    
    //Schedule The work
    SchedulerPlugIn.scheduleWork(scheduledJobDetail, trigger);

Where's the Work?

Now that we've decided on our Trigger, we can get on to scheduling our work. It seems like we're done, but all we did was schedule a job to run; we have yet to complete the most important step. Notice that HelloWorld.class was passed into the JobDetail constructor. This class is where the actual work is being done. HelloWorld extends the Quartz class Job and overrides the execute() method. execute() will get called when the Scheduler decides it is time to run this particular job. Let's look at the code.

import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;

//extend the proper Quartz class
public class HelloWorld extends Job {

    //override the execute method
    public void execute(JobExecutionContext context) {

    // Every job has it's own job detail
    JobDetail jobDetail = context.getJobDetail();
    // The name is defined in the job definition
    String jobName = jobDetail.getName();
    //Every job has a Job Data map for storing extra information
    JobDataMap dataMap = jobDetail.getJobDataMap();
       
       System.out.println("Hello World!!!");
   }
 }

For the sake of testing, you probably want to adjust the timing of the trigger to something a little more frequent, so that you can see your HelloWorld in action. After all, you probably don't want to have to wait until 2:00 a.m. to make sure the scheduled task has actually run. Instead, you might want a Trigger that runs every ten seconds:

Trigger trigger = new SimpleTrigger("trigger1", "group1");
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
trigger.setRepeatInterval(10000L); // milliseconds

Notice that this Trigger didn't use a cron-like syntax. Quartz has a variety of options and configuration methods to suit all of your scheduling needs.

Other Timing Configurations

Quartz provides many ways in which to schedule work. The CronTrigger is perhaps the most sophisticated, but there are several others. Most of the trigger mechanisms can be created using the TriggerUtils class provided by Quartz. Here are some other common examples of other Triggers in action. As the old saying goes, there's more than one way to skin a cat.

Triggers that Fire Every Day at 2:22 a.m.

// option 1: using makeDailyTrigger
Trigger trigger = TriggerUtils.makeDailyTrigger(2, 22);
trigger.setName("trigger1");
trigger.setGroup("group1");

// option 2: using CronTrigger
Trigger trigger = new CronTrigger("trigger1", "group1");
trigger.setCronExpression("0 22 2 * * ?");

A Trigger that Executes Every Five Seconds

/* option 1: makeSecondlyTrigger
 * Note that this will create a trigger that starts immediately.
 * To control the start time, use trigger.setStartTime(Date)
 */
Trigger trigger = TriggerUtils.makeSecondlyTrigger(5);
trigger.setName("MyFiveSecondTrigger");
trigger.setGroup("MyTriggerGroup");


 /* option 2: set repeat count and interval on simple trigger
 * Note that this will create a trigger that starts immediately.
 * To control the start time, use trigger.setStartTime(Date)
 */
Trigger trigger = new SimpleTrigger("trigger1", "group1");
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
trigger.setRepeatInterval(5000L); // milliseconds

Running a Job at Intervals

Trigger trigger = new SimpleTrigger("trigger1", "group1");
// 24 hours * 60(minutes per hour) *
// 60(seconds per minute) * 1000(milliseconds per second)
trigger.setRepeatInterval(24L * 60L * 60L * 1000L);

Conclusion

In this demonstration, we've only scratched the surface to what the Quartz framework can do. Keep in mind that Java 5 and J2EE 5 also have scheduling mechanisms, but they aren't as flexible and as easy to use as Quartz. Quartz is currently the only free open source Java library for doing these types of scheduling tasks, and it really makes a great addition to a developer's bag of tricks. You can download Quartz from Open Symphony and you can find an excellent tutorial and cookbook on the same site.

Resources

Chris Hardin is a Senior Java Architect in Birmingham, Alabama.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.