Friday, January 14, 2011

Integrating Quartz Scheduler with J2EE Web Applications.

Quartz is a full-featured, open source job scheduling service that can be integrated with, or used along side virtually any Java EE or Java SE application - from the smallest stand-alone application to the largest e-commerce system. Quartz can be used to create simple or complex schedules for executing tens, hundreds, or even tens-of-thousands of jobs; jobs whose tasks are defined as standard Java components that are programmed to fulfill the requirements of your application. The Quartz Scheduler includes many enterprise-class features, such as JTA transactions and clustering.  Quartz .


*) Download : 
   To download Quartz, visit http://www.quartz-scheduler.org/download/ and download the most recent stable release.


*) Setup:
1) Using Maven.

If you are using Maven use this in your pom.xml

      <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz</artifactId>
                <version>1.8.3</version>
      </dependency>


2) Manually setting up the jar files.

Firstly, download and unzip Quartz and copy the quartz-all-*.jar to your WEB-INF\lib folder.
Then copy all the jar's from the unzipped quartz lib folder in as well.
You would find quartz-x.x.x.tar.gz in the download.  Currently, the latest stable release is quartz-1.8.1.tar.gz. 
For Quartz to work with your web application make sure you have all the dependent jars in the Applications classpath or the Servers classpath.



*) Integrating with your Web Application :

Quartz can be used as a Job scheduler with any Java EE compliant applications.
The scheduler can be started in many different ways.


Option 1) Using ServletContextListener :

You could start the scheduler  when the Servlet Context is initialised. Most of the web applications already have a listener implemented to listen to Context initialised and destroyed events. If not, you could implement one as follows .

Make these entries in the web.xml.
    <listener>
    <listener-class>
             listener.ServletContextAppListener
    </listener-class>
    </listener>

Assuming the User defined class ServletContextAppListener resides in the listener package of your web application.

public class ServletContextAppListener implements ServletContextListener {
    Scheduler scheduler = null;
   
    @Override
    public void contextInitialized(ServletContextEvent pServletContext) {
       
        try {
            scheduler = (Scheduler) StdSchedulerFactory.getDefaultScheduler();
            scheduler.start();
        } catch (SchedulerException pScehdulerException) {
            pScehdulerException.printStackTrace();
        }

    }


    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
       
        try {
            scheduler.shutdown();
        } catch (SchedulerException pScehdulerException) {
            pScehdulerException.printStackTrace();
        }


    }

}





Now, when the Servlet container is init'ed the scheduler is started. It is a good time to set the scheduler in the ServletContext, so that they can be used in servlets (for example) to schedule jobs. We could setup the ServletContext in the Scheduler as well, so that the ServletContext is avaiable to the Scheduler and hence to the Jobs.



    @Override
    public void contextInitialized(ServletContextEvent pServletContext) {
        Log("[STARTED] Context inited now");
        try {
            scheduler = (Scheduler) StdSchedulerFactory.getDefaultScheduler();
           
            /* Setting the scheduler in the ServletContext */
            pServletContext.getServletContext().setAttribute("scheduler", scheduler);   

            /* Set up the ServletContext in the SchedulerContext */   
            scheduler.getContext().put("servletContext", pServletContext.getServletContext());     

            scheduler.start();
        } catch (SchedulerException pScehdulerException) {
            pScehdulerException.printStackTrace();
        }

    }


You can now access the ServletContext from the Job as follows :

public class EmailJob implements Job {
   
    /* execute() is called by the scheduler once every 5 minutes */
   
    @Override
    public void execute(JobExecutionContext pJobContext) throws JobExecutionException {
             ....
            /* Get the ServletContext set in the ContextInitialised callback above
           /* Note: As i write this blog i do not have access to my code which works. I write this         
              looking up  documentation from the Quartz website, there might be typos */
            ServletContext lServletContext =     
                                       (Servlet Context)pJobContext.getScheduler().getContext().get("servletContext");

    }
}


 Option 2) Using the built in QuartzInitializerListener (is part of version 1.8.4 ? or later ) :

Using the QuartzInitializerListener is a more elegant way of starting Quartz scheduler than option 1, although it uses the above mechanism. You can use this mechanism by making the following entries in quartz.properties and web.xml.

quartz.properties


org.quartz.scheduler.instanceName = MyJobScheduler
org.quartz.scheduler.instanceId = 1
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

# Add this to Configure Quartz to look in the quartz.xml for the job schedules
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
org.quartz.plugin.jobInitializer.fileNames = quartz.xml
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.scanInterval = 120


web.xml

<listener>
         <listener-class>
             org.quartz.ee.servlet.QuartzInitializerListener
         </listener-class>
</listener>


Instead of writing the listener (ServletContextAppListener), we use the built-in  org.quartz.ee.servlet.QuartzInitializerListener class to handle the instantiation and starting of the scheduler. You could pass in some init parameters to control the way the Scheduler is created and started. They are explained below.

Make the following entries in web.xml

    <context-param>
             <param-name>config-file</param-name>
             <param-value>quartz.properties</param-value>
         </context-param>
         <context-param>
            <param-name>shutdown-on-unload</param-name>
             <param-value>true</param-value>
         </context-param>
         <context-param>
             <param-name>start-on-load</param-name>
             <param-value>true</param-value>
         </context-param>
          <context-param>
             <param-name>start-delay-seconds</param-name>
             <param-value>10</param-value>
         </context-param>


All of the above init parameters are self explanatory. The first parameter points to the config file for Quartz quartz.properties. The last one start-delay-seconds is important in situations where you want the scheduler to start after a certian period of time. Otherwise the jobs may run immediately ( if configured to run so ) on start up of the scheduler. So, it is advisable to give the servlet container and your application some time to settle down before scheduling jobs to run.

If you would like to have the scheduler injected into the ServletContext (as in option 1)  you have to set another init parameter like below in web.xml :


     <context-param>
         <param-name>servlet-context-factory-key</param-name>
         <param-value>schedulerFactory</param-value>
     </context-param>


You can now access the Scheduler from the ServletContext like below from a servlet:

public class SomeServlet extends HttpServlet {
   
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)  {

        /* The schedulerFactory was injected into the ServletContext using the init parameters by QuartzInitializerListener */
        SchedulerFactory lSchedulerFactory = (SchedulerFactory)   .getSession().getServletContext().getAttribute("schedulerFactory");
       
        if(lSchedulerFactory == null ) {
            // Should not happen - Log an error and handle it
        }
       
        Scheduler lScheduler = null;

        /* Get the scheduler from the factory */
        try {
            lScheduler = lSchedulerFactory.getScheduler();
        } catch (SchedulerException pScehdulerException) {
            // Should not happen - Log an error and handle it
            pScehdulerException.printStackTrace();
        }

           // Setup the Job class and the Job group 
           JobDetail jobDetail = new JobDetail("EmailJob", Scheduler.DEFAULT_GROUP, EmailJob.class);

              // Create a Trigger that fires every 5 minutes.
              Trigger trigger = TriggerUtils.makeMinutelyTrigger(5);
       
              // Setup the Job and Trigger with the Scheduler and schedule jobs
              lScheduler.scheduleJob(jobDetail, trigger );


             // The servlet execution continues normally independent of the job
             ....


*) Writing the job class:

Below is a simple example of how to write a job which can be scheduled on the Quartz scheduler.   
You can convert a Java class into a Job which can be executed by the scheduler by making it implement the Job interface.
The Job interface is as below:

public interface Job {

    public void execute(JobExecutionContext context)
      throws JobExecutionException;
  }


Implement a Job to send Emails once every 5 minutes.

public class EmailJob implements Job {
   
    /* execute() is called by the scheduler once every 5 minutes */
   
    @Override
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        /* Find emails to be sent */
        List<Email> lEmailsNotYetSentList = getPendingEmails();
        sendEmails(lEmailsNotYetSentList);
    }
}


*) Programmatic Vs Declarative scheduling

Jobs can be scheduled to run programatically or using configuration from the XML file. The advantage of using a Declarative approach (XML Config file) is you do not need to rebuild and deploy the application everytime you make a change to the XML Config file. So you can add, delete and modify jobs without rebuilding the application. A restart of the application is all it takes to get the changes working.

The job to schedule is EmailJob. In Quartz, there are 2 basic concepts :

1) Job : Creating the Job is the first step, but to get your Job called by the Scheduler, you need to indicate to the Scheduler how often,and at what time, your Job should be called. <BR>
2) Trigger  :This is done by associating a Trigger to the Job. A Trigger has enough information about when to schedule a job. There is a utility class called TriggerUtils which is quite handy to create Triggers.



Option 1) Programmatic scheduling example.

The following example schedules a job from a Servlet. When jobs are started from a Servlet it does not run in the context of
the Servlet. This is one good reason not to create a Thread from a Servlet, which is not considered to be a good practice anyway.That is the reason we inject a reference to the Scheduler in the context initialised callback. Please note that without injecting the scheduler into the ServletContext you wouldn't have had access to it here unless you some other mechanism provided by the servlet container to access such resources.


    // Get the scheduler which we setup in the ContextInitialised event
    Scheduler lScheduler = (Scheduler)req.getServletContext().getAttribute("scheduler);

    JobDetail jobDetail = new JobDetail("EmailJob", Scheduler.DEFAULT_GROUP, EmailJob.class);

    // Create a Trigger that fires every 5 minutes.
        Trigger trigger = TriggerUtils.makeMinutelyTrigger(5);
       
        // Setup the Job and Trigger with the Scheduler
        lScheduler.scheduleJob(jobDetail, trigger );


    // The servlet execution continues normally independent of the job
    ....



Option 2) Declarative scheduling example.

The main reason you would take this approach is, it is easier to deploy the application without going for a a re-build. You can make the changes to the xml config file
and see the results with a restart of the server. In the previous example the job is scheduled from a servlet. It used Java code to setup the Job and Trigger with the Scheduler. The Quartz framework also supports setting up the Job schedule declaratively in XML files. A declarative approach allows us to more quickly modify which Jobs are being executed and when. You could change a job's trigger and make it run every 30 minutes from an hour without changing any source code.

The Quartz framework includes a "plugin" that reads an XML file containing Job and Trigger information upon startup of a Quartz application. All Jobs within the XML are added to the Scheduler, along with their Triggers. You will still have to write those java classes implementing the org.quartz.Job interface to do your work from within the job. Only thing that changes is the schedule these jobs run can be controlled externally via the XML file.

Following is a snapshot of <code>quartz.xml</code> which has a job (email-job) configured to run every 5 minutes using a cron expression. More on cron expressions here - Cron Expression Wiki


<?xml version='1.0' encoding='utf-8'?>
<job-scheduling-data xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd"
  version="1.8">
  <schedule>
        <job>
            <name>email-job</name>
            <group>EMAIL_JOBS_GROUP</group>

            <description>Creates a PDF and persists the payload to the database</description>
            <job-class>jobs.EmailJob</job-class>
            <job-data-map>
               <entry>
                    <key>SMTP_HOST</key>
                    <value>10.x.x.x</value>
                </entry>
            </job-data-map>
        </job>

        <trigger>
            <cron>
                <name>email-trigger</name>
                <group>EMAIL_TRIGGERGROUP</group>
                <job-name>email-job</job-name>

                <job-group>EMAIL_JOBS_GROUP</job-group>
                <!-- trigger every 5 minutes !-->
            <cron-expression>0 */5 * * * ?</cron-expression>
            </cron>
        </trigger>

    </schedule>
</job-scheduling-data>


The EmailJob is triggered every 5 minutes.

It is a good approach to wrap all the calls to the Quartz API, so that developers do not use it directly. Writing helper classes around the API could be useful for debugging and bookkeeping purposes. Hope this blog helped someone somewhere schedule jobs :) !

6 comments:

  1. For the sake of your readers and your self, please name your variables more readable. It's horrible to name local variable with Capital letter, and with all those prefixes.

    Any how, thanks for the write up. Good intro. One comment is your option didn't mention a equivalent example on how to add ServletContext into the scheduler's context and retriving it back inside a job, just like you mentioned in option 1.

    ReplyDelete
  2. Thanks for your feedback. About the naming conventions everyone of us have their own school of thought. I might think the same of your naming conventions, but wouldn't complain about it. I would rather try to understand the concepts presented in the blog , take what is interesting and forget about the rest.

    And about the comment, i couldn't figure out a way of doing it in Option 2.

    ReplyDelete
  3. Hello Amith,

    I am using the first option, it works for me, thanks!

    Best regards,

    Luis

    ReplyDelete
  4. option2 is not working and throwing exception 'org.quartz.SchedulerException: SchedulerPlugin class 'org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin' could not be instantiated. [See nested exception: java.lang.ClassNotFoundException: Unable to load class org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin by any known loaders.]
    '

    ReplyDelete
  5. Quartz is a full-featured, open source job scheduling service that can be integrated with, or used along side virtually any Java EE or Java SE application - from the smallest stand-alone application to the largest e-commerce system.
    very nice info .
    java training in chennai | java training in chennai velachery |
    java j2ee training institutes in velachery

    ReplyDelete

Followers

About Me

I'm a software developer with interests in Design Patterns, Distributed programming, Big Data, Machine Learning and anything which excites me. I like to prototype new ideas and always on the lookout for tools which help me get the job done faster. Currently, i'm loving node.js + Mongodb.