Job Scheduling in Java
Pages: 1, 2, 3, 4

Adaptable in Every Environment

All of the above applies to using Quartz in a standalone application. Now, we will see how we can use its interface in some of the most common environments for Java developer.



RMI

With a distributed application using RMI, Quartz is simple, as we've seen above. The differences are in the configuration.

There are two necessary steps: first we have to set Quartz as an RMI server that will handle our requests, and then we just use it in the standard manner.

The source code of this example is practically the same as in our first example, but here we will divide it in two parts: scheduler initialization and scheduler usage.

package net.nighttale.scheduling.rmi;

import org.quartz.*;

public class QuartzServer {

  public static void main(String[] args) {
	
    if(System.getSecurityManager() != null) {
      System.setSecurityManager(
        new java.rmi.RMISecurityManager()
      );
    }
	   
    try {
      SchedulerFactory schedFact =
       new org.quartz.impl.StdSchedulerFactory();
      Scheduler sched = schedFact.getScheduler();
      sched.start();	
    } catch (SchedulerException se) {
      se.printStackTrace();
    }
  }
}

As you can see, the code for scheduler initialization is standard except that contains a part that sets the security manager. The key is in the configuration file (quartzServer.properties), which now looks like this:

#
# Configure Main Scheduler Properties 
#
org.quartz.scheduler.instanceName = Sched1
org.quartz.scheduler.rmi.export = true
org.quartz.scheduler.rmi.registryHost = localhost
org.quartz.scheduler.rmi.registryPort = 1099
org.quartz.scheduler.rmi.createRegistry = true

#
# Configure ThreadPool 
#

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 5
org.quartz.threadPool.threadPriority = 4

#
# Configure JobStore 
#

org.quartz.jobStore.misfireThreshold = 5000

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

Notice the highlighted lines, which indicate that this scheduler should export its interface through RMI and provide parameters for running the RMI registry.

We have do a few more things to successfully deploy our server, all of which are typical tasks for exporting objects through RMI. First, we need to start rmiregistry on the server (if it is not already up). This is done by calling:

   rmiregistry &

on Unix systems, or

   start rmiregistry

on Windows platforms.

Next, start the QuartzServer class with the following options (all on one line):

java  -Djava.rmi.server.codebase
   file:/home/dejanb/quartz/lib/quartz.jar
   -Djava.security.policyrmi.policy
   -Dorg.quartz.propertiesquartzServer.properties
   net.nighttale.scheduling.rmi.QuartzServer

Now, let's clarify these parameters a little bit. Quartz's Ant build tasks include an rmic call to create the necessary RMI classes, so to point clients to the codebase with these classes, you have to start it with the -Djava.rmi.server.codebase parameter set to file: plus the full path to the location of quartz.jar (of course, this could also be a URL to the library).

An important issue in a distributed system is security; thus, RMI enforces that you use security policy. In this example, we used the basic policy file (rmi.policy) that grants all privileges.

grant {
  permission java.security.AllPermission;
};

In practice, you should adapt this policy to your system security needs.

OK, now the Scheduler is ready to accept your Jobs via RMI. Let's write the client that will do that.

package net.nighttale.scheduling.rmi;

import org.quartz.*;
import net.nighttale.scheduling.*;

public class QuartzClient {

  public static void main(String[] args) {
    try {
      SchedulerFactory schedFact =
       new org.quartz.impl.StdSchedulerFactory();
      Scheduler sched = schedFact.getScheduler();
      JobDetail jobDetail = new JobDetail(
        "Income Report",
        "Report Generation",
        QuartzReport.class
      );
		
      CronTrigger trigger = new CronTrigger(
        "Income Report",
        "Report Generation"
      );
      trigger.setCronExpression(
        "0 0 12 ? * SUN"
      );
      sched.scheduleJob(jobDetail, trigger);
    } catch (Exception e) {
      e.printStackTrace();
    }
   }
}

As you can see, the source for the client is literally the same as before. Of course, there are different settings for quartzClient.properties here, too. All you have to do is to specify that this scheduler is the RMI client (proxy) and the location of the registry where it should look for the server.

# Configure Main Scheduler Properties  

org.quartz.scheduler.instanceName = Sched1
org.quartz.scheduler.rmi.proxy = true
org.quartz.scheduler.rmi.registryHost = localhost
org.quartz.scheduler.rmi.registryPort = 1099

No other settings are necessary, because all the work is done on the server side. In fact, if other settings are present, they will be ignored. The important thing is that the names of the schedulers must match in the client and server configurations (Sched1 in this case). And that's all there is, for a start. Just run the client with redirected properties file (again, all on one line):

java -Dorg.quartz.properties
       quartzClient.properties
       net.nighttale.scheduling.rmi.QuartzClient

and you should expect the same behavior in the server console as in the first example.

Web and Enterprise

If you are developing a web or enterprise solution, one question that may come up is the right place to start the scheduler. For that, Quartz provides org.quartz.ee.servlet.QuartzInitializerServlet. All we need to do is to make the following configuration 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>
</servlet>

If you want to call the EJB method as a Job, you should pass the org.quartz.ee.ejb.EJBInvokerJob class to your JobDetail. To demonstrate this technique, we will implement ReportGenerator as a session bean and call its generateReport() method from the servlet.

package net.nighttale.scheduling.ee;

import java.io.IOException;

import javax.servlet.*;
import net.nighttale.scheduling.Listener;
import org.quartz.*;
import org.quartz.ee.ejb.EJBInvokerJob;
import org.quartz.impl.StdSchedulerFactory;

public class ReportServlet implements Servlet {

  public void init(ServletConfig conf)
    throws ServletException {
    JobDetail jobDetail = new JobDetail(
      "Income Report",
      "Report Generation",
      EJBInvokerJob.class
    );
    jobDetail.getJobDataMap().put(
      "ejb",
      "java:comp/env/ejb/Report"
    );
    jobDetail.getJobDataMap().put(
      "method",
      "generateReport"
    );
    Object[] args = new Object[0];
    jobDetail.getJobDataMap().put("args", args);
    CronTrigger trigger = new CronTrigger(
      "Income Report",
      "Report Generation"
    );
    try {
      trigger.setCronExpression(
        "0 0 12 ? * SUN"
      );
      Scheduler sched =
       StdSchedulerFactory.getDefaultScheduler();
      sched.addGlobalJobListener(new Listener());
      sched.scheduleJob(jobDetail, trigger);
      System.out.println(
        trigger.getNextFireTime()
      );
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public ServletConfig getServletConfig() {
    return null;
  }

  public void service(ServletRequest request,
                      ServletResponse response)
    throws ServletException, IOException {
  }

  public String getServletInfo() {
    return null;
  }

  public void destroy() {
  }
}

As you can see, there are three parameters that needs to be passed to the job.

  • ejb: The JNDI name of the enterprise bean.
  • method: The actual method to be called.
  • args: An array of objects to be passed as a method arguments.

Everything else remains the same from the Quartz usage perspective. I have put this example in the servlet initialization method for simplicity, but of course, it could be done from any convenient place in your application. In order to successfully run such a job you'll need to register this EJB with your web application. This is usually done by putting the following lines in the web.xml file.

<ejb-ref>
  <ejb-ref-name>ejb/Report</ejb-ref-name>
  <ejb-ref-type>Session</ejb-ref-type>
  <home>net.nighttale.scheduling.ee.ReportHome</home>
  <remote>net.nighttale.scheduling.ee.Report</remote>
  <ejb-link>ReportEJB</ejb-link>
</ejb-ref>

Some application servers (such as Orion) need to be instructed to allow creation of user threads, and thus would need to be started with the -userThread switch.

These are by no means all the enterprise features of Quartz, but it's a good start and you should look at Quartz's Javadocs for further questions.

Web Services

Quartz currently has no built-in support for being used as a web service, but you can find a plug-in that enables you to export the Scheduler interface through XML-RPC. Building an installation procedure is simple. To start, you have to extract the plug-in source into the Quartz source folder and rebuild it. Plug-ins rely on the Jakarta XML-RPC library, so you have to be sure that it is in the classpath. Next, add the following lines in the properties file.

org.quartz.plugin.xmlrpc.class = org.quartz.plugins.xmlrpc.XmlRpcPlugin
org.quartz.plugin.xmlrpc.port = 8080

Now the Scheduler is ready to be used through XML-RPC. This way you can use some of the Quartz features from different languages such as PHP or Perl, or in a distributed environment where RMI might not be the right solution.

Summary

We've looked at two ways to do scheduling in Java. Quartz is indeed a powerful library, but for simple requirements the Timer API can save the day, keeping you from putting unnecessary complexity into the system. You should think of using the Timer API in cases where you don't have a lot of tasks that need to be scheduled, and when their execution times are well-known (and unchangeable) in the design process. In these situations, you don't have to worry whether some tasks are lost because of a shutdown or crash. For more sophisticated needs, Quartz is an elegant scheduling solution. Of course, you can always make your own framework, based on the Timer, but why to bother when all that (and much more) already exist?

One event worth noting is IBM and BEA's submission of JSR 236 titled "Timer for Application Servers". This specification is focused on creating an API for timers in managed environments where using the standard API is insufficient. You can find more details about the specification on IBM's developerWorks site, and the specification should be available for public review in spring.

Sample Code

ReportGenerator.zip

Dejan Bosanac is a software developer, technology consultant and author. He is focused on the integration and interoperability of different technologies, especially the ones related to Java and the Web.


Return to ONJava.com.