Wednesday, March 24, 2010

Using the Quartz Enterprise Scheduler in J2EE

http://www.theserverside.com/news/1377023/Using-the-Quartz-Enterprise-Scheduler-in-J2EE

Before we dive down the details let's assume that you have a business use case that requires a Job to run every 30 minutes. In this article we will discuss how can you achieve this using the Cron Triggers functionality of Quartz.

Define Your Job as an EJB Method

The first step in scheduling a job in J2EE application is to create the EJB and implement the business logic as an EJB Method. For example, I have created a stateless EJB named TestEJB and I've a method named yourMethod that I want to schedule as a job. For clarity, following is the code snippet of my EJB bean implementation class and EJB deployment descriptor:

package howto.quartz.ejb;
import java.rmi.*;
import javax.ejb.*;
public class TestEJBBean implements SessionBean {
  public TestEJBBean() {
  }
  // EJB life cycle methods are omitted for brevity
  ........
  public void yourMethod() throws RemoteException {
    System.out.println("TestEJB Job");
  }
}


Use Quartz API to Schedule Your Job from a Generic Servlet

Quartz uses its own thread pool and these threads are not container threads. Servlet API allows user threads and hence you have to create a Servlet and use Quartz API to schedule your Job. Quartz provides the QuartzInitializerServlet to be used as the entry point for the Quartz job scheduling service. We want to submit the yourMethod of TestEJB as the job in this example. So we will have to create a GenericServlet named howto.quartz.servlet.QuartzServlet and implement the init() method that submits EJB method as a Cron Trigger. In this example, I'm setting the cron expression as an initialization parameter instead of hard coding in the Servlet. The following is the code for the Servlet:

public class QuartzServlet extends GenericServlet {
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    System.out.println("Scheduling Job ..");
    JobDetail jd = new JobDetail("Test Quartz","My Test Job",EJBInvokerJob.class);
    jd.getJobDataMap().put("ejb", "java:comp/env/ejb/TestEJB");
    jd.getJobDataMap().put("method", "yourMethod");
    Object[] jdArgs = new Object[0];
    jd.getJobDataMap().put("args", jdArgs);
    CronTrigger cronTrigger = new CronTrigger("Test Quartz", "Test Quartz");

    try {
      String cronExpr = null;
      // Get the cron Expression as an Init parameter
      cronExpr = getInitParameter("cronExpr");
      System.out.println(cronExpr);
      cronTrigger.setCronExpression(cronExpr);
      Scheduler sched = StdSchedulerFactory.getDefaultScheduler();
      sched.scheduleJob(jd, cronTrigger);
      System.out.println("Job scheduled now ..");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
  }
  public String getServletInfo() {
    return null;
  }
}


Auto starting the Servlets

We want the job to be submitted as soon the application is deployed or the container is started. We have to initialize the QuartzInitializerServlet and howto.quartz.servlet.QuartzServlet that we implemented whenever the web module is restarted. In order to achieve that we need to create the following entry in the deployment descriptor for the web module (web.xml):

<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>
<servlet>
 <servlet-name>QuartzServlet</servlet-name>
 <display-name>Quartz Servlet</display-name>
 <servlet-class>howto.quartz.servlet.QuartzServlet</servlet-class>
 <init-param>
  <param-name>cronExpr</param-name>
  <param-value>0 0/30 * * * ?</param-value>
 </init-param>
 <load-on-startup>2</load-on-startup>
</servlet>

The Servlet accesses the TestEJB so we need to create the ejb-ref entry in the web.xml as follows:

<ejb-ref>
 <ejb-ref-name>ejb/TestEJB</ejb-ref-name>
 <ejb-ref-type>Session</ejb-ref-type>
 <home>howto.quartz.ejb.TestEJBHome</home>
 <remote>howto.quartz.ejb.TestEJB</remote>
</ejb-ref>


Assembling/Packaging the Application

The web module accesses the Quartz API so we need to package the Quartz libraries in the WAR module. We need to put these libraries e.g. quartz.jar, commons-logging.jar, commons-pool-1.1.jar, etc. in the WEB-INF/lib of the WAR module. The quartz settings that you need for your environment must be specified in the quartz.properties and this file must be put in the WEB-INF/classes directory of the WAR module.

The ejb-jar that contains the TestEJB and the WAR containing the Quartz libraries and QuartzServlet needs to be packaged as an EAR and deployed to the J2EE container.

Note:
For OC4J, you need to configure it to allow User Threads.
Configure your J2EE container to allow user threads to be created by your application as Quartz scheduler creates threads those are treated as user threads. For example, you need to start OC4J as follows to allow user threads:

java -jar oc4j.jar -userThreads


Deploy Your J2EE Application

Then deploy your application in your J2EE container. Make sure that your application is set automatically started when your application server is started. For example, if you are using OC4J make sure that you set the auto-start for your application and load-on-startup for the web module to true. To be sure make sure that you have the following entries in the server configuration files:

server.xml:

<application name="quartz" path="../applications/quartz-app.ear" auto-start="true" />

http-web-site.xml:

<web-app application="quartz" name="quartz-web" load-on-startup="true" root="/quartz" />

Now you are ready to go!

Your EJB method now is scheduled as a recurring Job that occurs in every 30 minutes.

1 comment:

Umang Jain said...

Thank you... it was helpful,...