Wednesday, July 27, 2011

Quartz 2.0 with Spring

I was entrusted with writing a scheduler, which is to run at a fixed time daily and it was decided to use quartz for this purpose. First the task was done using spring integration classes for quartz. But unfortunately, problems arose. Quartz 2.x versions have been refactored and no longer can be used with spring integration support. Some of the classes of older versions of quartz have been made interfaces in version 2.0.

Since I couldn't find a comprehensive example of how to schedule a task using quartz 2.0, I decided to post this example, hoping it would be useful to someone.Initialization of the scheduler is done in the init() method and the actual task need to be done is written in the inner class, AdminJob.

The following code schedules a task, which will be run daily at a fixed time.
=====================================================

package com.shyarmal.admin.jobs;

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

import java.util.List;

import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AdminTask {

    private static NotificationSender notificationSender;
    private static Client client;
    private String cronExpression;
    private static final String JOB_NAME = "admin-job";
    private StdSchedulerFactory stdSchedulerFactory;
    private Scheduler scheduler;
    private static final Logger LOGGER = LoggerFactory.getLogger(AdminTask.class);
  
    public void init() {
        try {
            LOGGER.info("initializing admin notify scheduler .... ");
            scheduler = stdSchedulerFactory.getScheduler();
            if(scheduler.checkExists(JobKey.jobKey(JOB_NAME))) {
                scheduler.deleteJob(JobKey.jobKey(JOB_NAME));
            }
            JobDetail job =  newJob(AdminJob.class).withIdentity(JOB_NAME).build();

            CronTrigger trigger = newTrigger()
                    .withIdentity("admin-notify", "priority")
                    .withSchedule(cronSchedule(cronExpression)).forJob(JOB_NAME)
                    .build();
          
            scheduler.scheduleJob(job, trigger);
            LOGGER.info("initialized admin notify scheduler .... ");
        } catch (Exception e) {
            LOGGER.warn("scheduler initialization failed .... ", e);
        }
    }
  
    public void destroy() {
        try {
            LOGGER.info("interrupting job ... ");
            scheduler.deleteJob(JobKey.jobKey(JOB_NAME));
        } catch (Exception e) {
            LOGGER.warn("couldn't interrupt job ... ", e);
        }
    }

  
    public static class AdminJob implements Job {

        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
          
             List<Notification> notifications = notificationSender.create();
             if (!notifications.isEmpty()) {
                 LOGGER.info("Sending notification .... ");
                 if(!client.send(notifications.get(0))){
                     LOGGER.error("failed to send notification ... ");
                 }
             }
        }

    }

    public void setNotificationSender(NotificationSender notificationSender) {
        AdminTask.notificationSender = notificationSender;
    }

    public void setClient(Client client) {
        AdminTask.client = client;
    }

    public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
    }

    public void setStdSchedulerFactory(StdSchedulerFactory stdSchedulerFactory) {
        this.stdSchedulerFactory = stdSchedulerFactory;
    }
  
}

=====================================================

What happens in init()

A scheduler is obtained from the standard sheduler factory (injected through spring) and if a job by the name, admin-job exists it will be removed. A new JobDetail is created with the job key, admin-job. Then a CronTrigger (with trigger key, admin-notify and group, priority) is created for the admin-job. The cron expression is configured. Then the job is scheduled.

What happens in destroy()

The job created is deleted here. This is not guaranteed to run since sometimes the system will not be shut down cleanly.

The inner class, AdminJob

 This is the job class used when creating the JobDetial instance. It should implement org.quartz.Job and override the method, execute(JobExecutionContext), which is called by the scheduler at the time scheduled. So the actual work to be carried out goes in here. An important thing to note is that this class has to be public and static (if not instantiation of it fails).

The reason for using an inner class rather than a separate class was to access the fields injected to the outer class (AdminTask) to be directly accessible. If not the corresponding fields will be needed to put in the JobDataMap of JobDetail and access via the JobExecutionContext instance passed to execute(JobExecutionContext). The major issue here is the contents of JobDataMap need to be serializable. 


The spring configuration of AdminTask and quartz standard scheduler factory are as follows. The cron expression can be configured through a properties file. When making the scheduler factory I have passed the property, org.quartz.jobStore.class to be org.quartz.simpl.RAMJobStore. This is to make my triggers run in the memory (RAM), so that they'll be destroyed when the system is turned off.
=====================================================

    <bean id="adminTask" class="com.shyarmal.admin.jobs.AdminTask"
        init-method="init"
        destroy-method="destroy"
        p:client-ref="client"
        p:notificationSender-ref="notificationSender"
        p:cronExpression="${prop.admin.task.cron.expr}"
        p:stdSchedulerFactory-ref="stdSchedulerFactory"/>

      
    <bean id="stdSchedulerFactory" class="org.quartz.impl.StdSchedulerFactory">
        <constructor-arg type="java.util.Properties" ref="quartzProperties"/>
    </bean>
  
    <util:properties id="quartzProperties">
        <prop key="org.quartz.jobStore.class">${prop.org.quartz.jobStore.class}</prop>
    </util:properties>

=====================================================
properties used in the properties file
=====================================================

prop.admin.task.cron.expr = 0 15 10 * * ? *
prop.org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

thanks,
Shyarmal.

No comments:

Post a Comment