Friday, December 12, 2014

Quartz Scheduler and Cron Job/Trigger in Spring

Quartz Scheduler and CRON job in Spring Application

Steps to implement Quartz Scheduler in Spring application.

Step 1. Include the following dependency inclusion into your pom.xml


<!-- Quartz framework dependencies -->

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


Step 2:  Write scheduler job class



package ****.util;

import java.util.Date;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerContext;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;

/**
 * information about cim renew reminder job
 * @author velmurugan pousel
 * @created on Dec 11, 2014
 *
 */
public class CIMRenewReminderJob extends QuartzJobBean  {

    private static final Log LOG = LogFactory.getLog(CIMRenewReminderJob.class);
   
    private CIMParametersService cIMParametersService;
   
    @Override
    protected void executeInternal(JobExecutionContext context)
            throws JobExecutionException {

        SchedulerContext schedulerContext;
        try {
            if (LOG.isDebugEnabled()) {
                LOG.info("Entering into method executeInternal of class SchoolScoreFTPJobDetail");
            }

            schedulerContext = context.getScheduler().getContext();

            BeanFactory beanFactory = (BeanFactory) schedulerContext
                    .get("applicationContext");

            if (cIMParametersService == null) {
                cIMParametersService = (CIMParametersService) beanFactory
                        .getBean("cIMParametersService");

            }

            sendRenewReminders();

        } catch (Exception e) {
            LOG.error(" Error on CIMRenewReminderJob ", e);
        }

        if (LOG.isDebugEnabled()) {
            LOG.info("Exiting from method executeInternal of class CIMRenewReminderJob");
        }

    }
   
    /**
     * send out the CIM renew reminder notifications
     *
     * @return true or false
     */
    private boolean sendRenewReminders() {

        LOG.info(" ################## CIM Renew Rimnder job triggered on " + new Date());

        return false;
    }

}


Step 3:   Define the job details in xml file along with the cron trigger details to trigger this job based on the expression.


applicationContext-schedular.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                        http://www.springframework.org/schema/util
                        http://www.springframework.org/schema/util/spring-util-3.1.xsd
                        http://www.springframework.org/schema/jee
                        http://www.springframework.org/schema/jee/spring-jee-3.1.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd">

   
    <bean id="scheduler"
        class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
        lazy-init="false">
        <property name="dataSource" ref="dataSource" />
        <property name="overwriteExistingJobs" value="true" />
        <property name="autoStartup">
            <value>true</value>
        </property>
        <property name="jobFactory">
            <bean class="org.springframework.scheduling.quartz.SpringBeanJobFactory" />
        </property>
        <property name="applicationContextSchedulerContextKey">
            <value>applicationContext</value>
        </property>
        <property name="waitForJobsToCompleteOnShutdown">
            <value>true</value>
        </property>
        <property name="transactionManager" ref="transactionManager" />
        <property name="quartzProperties">
            <props>
                <prop key="org.quartz.scheduler.classLoadHelper.class">org.quartz.simpl.CascadingClassLoadHelper</prop>
                <prop key="org.quartz.scheduler.instanceName">MCATSRSScheduler</prop>
                <prop key="org.quartz.scheduler.instanceId">AUTO</prop>
                <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
                <prop key="org.quartz.threadPool.threadCount">20</prop>
                <prop key="org.quartz.threadPool.threadPriority">5</prop>
                <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
                <prop key="org.quartz.jobStore.misfireThreshold">60000</prop>               
                <prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
                </prop>
                <prop key="org.quartz.jobStore.useProperties">false</prop>
                <prop key="org.quartz.jobStore.selectWithLockSQL">SELECT * FROM {0}LOCKS WHERE SCHED_NAME = {1} AND
                    LOCK_NAME = ? FOR UPDATE</prop>
                <prop key="org.quartz.jobStore.isClustered">true</prop>
                <prop key="org.quartz.jobStore.clusterCheckinInterval">20000</prop>
            </props>
        </property>
       
        <property name="jobDetails">
            <list>
                <ref bean="renewReminderJob" />           
            </list>
        </property>
 
        <property name="triggers">
            <list>   
                <ref bean="renewReminderTrigger" />     
            </list>
        </property>
    </bean>
   
    <!--     JOB CONFIGURATION FOR FTP JOB  -->        
       
    <bean id="renewReminderTrigger"
        class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="renewReminderJob" />
      <!--<property name="cronExpression" value="0 30 10 ? * *" />  -->
          <property name="cronExpression" value="0 0/10 * ? * MON-FRI" />
          <!-- this job would be triggered on every 10 mins ->
    </bean>    
        
    <bean name="renewReminderJob"
        class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass"
            value="org.*.cim.service.subs.util.CIMRenewReminderJob" />
        <property name="group" value="REMINDER_JOB" />
    </bean>
         
   
</beans>

Step4: Include your applciationContext-schedular.xml in applicationContaxt.xml



<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd
                        http://www.springframework.org/schema/jee
                         http://www.springframework.org/schema/jee/spring-jee-2.5.xsd">

    <context:component-scan base-package="org.*.subs" />

    <jee:jndi-lookup id="pouselEnv" jndi-name="cell/persistent/misc/pouselEnv" default-value="localhost" />

     <jee:jndi-lookup id="servicesUrl" jndi-name="cell/persistent/misc/servicesUrl" default-value="http://services2.development.pouselv.org"/>

     <bean id="envProperties" class="org.pouselv.commons.spring.util.JNDIPropertiesFactoryBean">
        <property name="location" ref="pouselEnv"/>
     </bean>


    <import resource="applicationContext-resources.xml" />
    <import resource="applicationContext-security.xml" />
    <import resource="applicationContext-hibernate.xml" />
 <import resource="applicationContext-services.xml" />   
      

   <import resource="applicationContext-scheduler.xml" />
   
   
</beans>

Step 4: Create the following tables in your DB schema to store and keep track of the quartz scheduler and job details.



DROP TABLE QRTZ_SIMPLE_TRIGGERS;
DROP TABLE QRTZ_CRON_TRIGGERS;
DROP TABLE QRTZ_SIMPROP_TRIGGERS;
DROP TABLE QRTZ_CALENDARS;
DROP TABLE QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE QRTZ_FIRED_TRIGGERS;
DROP TABLE QRTZ_SCHEDULER_STATE;
DROP TABLE QRTZ_LOCKS;
DROP TABLE QRTZ_TRIGGERS;
DROP TABLE QRTZ_JOB_DETAILS;

CREATE TABLE QRTZ_JOB_DETAILS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    JOB_NAME  VARCHAR2(200) NOT NULL,
    JOB_GROUP VARCHAR2(200) NOT NULL,
    DESCRIPTION VARCHAR2(250) NULL,
    JOB_CLASS_NAME   VARCHAR2(250) NOT NULL,
    IS_DURABLE VARCHAR2(1) NOT NULL,
    IS_NONCONCURRENT VARCHAR2(1) NOT NULL,
    IS_UPDATE_DATA VARCHAR2(1) NOT NULL,
    REQUESTS_RECOVERY VARCHAR2(1) NOT NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR2(200) NOT NULL,
    TRIGGER_GROUP VARCHAR2(200) NOT NULL,
    JOB_NAME  VARCHAR2(200) NOT NULL,
    JOB_GROUP VARCHAR2(200) NOT NULL,
    DESCRIPTION VARCHAR2(250) NULL,
    NEXT_FIRE_TIME NUMBER(13) NULL,
    PREV_FIRE_TIME NUMBER(13) NULL,
    PRIORITY NUMBER(13) NULL,
    TRIGGER_STATE VARCHAR2(16) NOT NULL,
    TRIGGER_TYPE VARCHAR2(8) NOT NULL,
    START_TIME NUMBER(13) NOT NULL,
    END_TIME NUMBER(13) NULL,
    CALENDAR_NAME VARCHAR2(200) NULL,
    MISFIRE_INSTR NUMBER(2) NULL,
    JOB_DATA BLOB NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
      REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_SIMPLE_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR2(200) NOT NULL,
    TRIGGER_GROUP VARCHAR2(200) NOT NULL,
    REPEAT_COUNT NUMBER(7) NOT NULL,
    REPEAT_INTERVAL NUMBER(12) NOT NULL,
    TIMES_TRIGGERED NUMBER(10) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
      REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CRON_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR2(200) NOT NULL,
    TRIGGER_GROUP VARCHAR2(200) NOT NULL,
    CRON_EXPRESSION VARCHAR2(120) NOT NULL,
    TIME_ZONE_ID VARCHAR2(80),
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
      REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
  (         
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_NAME VARCHAR(200) NOT NULL,
    TRIGGER_GROUP VARCHAR(200) NOT NULL,
    STR_PROP_1 VARCHAR(512) NULL,
    STR_PROP_2 VARCHAR(512) NULL,
    STR_PROP_3 VARCHAR(512) NULL,
    INT_PROP_1 NUMBER(10) NULL,
    INT_PROP_2 NUMBER(10) NULL,
    LONG_PROP_1 NUMBER(13) NULL,
    LONG_PROP_2 NUMBER(13) NULL,
    DEC_PROP_1 NUMERIC(13,4) NULL,
    DEC_PROP_2 NUMERIC(13,4) NULL,
    BOOL_PROP_1 VARCHAR(1) NULL,
    BOOL_PROP_2 VARCHAR(1) NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CALENDARS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    CALENDAR_NAME  VARCHAR2(200) NOT NULL,
    CALENDAR BLOB NOT NULL,
    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    TRIGGER_GROUP  VARCHAR2(200) NOT NULL,
    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_FIRED_TRIGGERS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    ENTRY_ID VARCHAR2(95) NOT NULL,
    TRIGGER_NAME VARCHAR2(200) NOT NULL,
    TRIGGER_GROUP VARCHAR2(200) NOT NULL,
    INSTANCE_NAME VARCHAR2(200) NOT NULL,
    FIRED_TIME NUMBER(13) NOT NULL,
    PRIORITY NUMBER(13) NOT NULL,
    STATE VARCHAR2(16) NOT NULL,
    JOB_NAME VARCHAR2(200) NULL,
    JOB_GROUP VARCHAR2(200) NULL,
    IS_NONCONCURRENT VARCHAR2(1) NULL,
    REQUESTS_RECOVERY VARCHAR2(1) NULL,
    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
CREATE TABLE QRTZ_SCHEDULER_STATE
  (
   SCHED_NAME VARCHAR(120) NOT NULL,
    INSTANCE_NAME VARCHAR2(200) NOT NULL,
    LAST_CHECKIN_TIME NUMBER(13) NOT NULL,
    CHECKIN_INTERVAL NUMBER(13) NOT NULL,
    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
CREATE TABLE QRTZ_LOCKS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME  VARCHAR2(40) NOT NULL,
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);

CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);

CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);

commit;




Reference:

CRON Expressions  



The Cron-Expressions are strings which are used for configuring the instances of CronTrigger. The Cron-Expressions made up of following sub-expressions that performs individual works according to it's schedule and that is separated by the white-space. :
  1. Seconds
  2. Minutes
  3. Hours
  4. Day-of-Month
  5. Month
  6. Day-of-Week
Example: The Cron-expression string is "0  0  10  ?  *  SUN" that means "every Sunday at 10 am". This example reads only the "SUN" from weekday and replaces to all weekday.
The Wild-cards ('* ' character) that can be used for inserting the every possible value of this field. The '*' character is used in the "Month" field that means "every month" and "Day-Of-Week" field means "every day of the week".
All fields have some specific values that are specified by us. Such as the numbers 0 to 23 for hours, 0 to 59 that is used for minutes and seconds, 0 to 31 for Day-of-Month but here, we should more careful about how many day are used in a month. Months have the specified values between 0 to 11, for this we will use the some string as like: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV and DEC. Similarly, the Days-of-week has specified values between 1 to 7. Here 1 for Sunday, 2 for Monday, 3 for Tuesday,....... so on. But here we will use the some string for this like: SUN, MON, TUE, WED, THU, FRI and SAT.
The Cron-Expressions are used the '/' character for increments to specify values. If we will put "0/10" in the seconds field that means every 10 seconds will start at second zero and if we will put "0/20" in the Minutes field then it will simply fires every 20 minutes.
The Cron-Expressions are also used the '?' character that allows for the day-of-month and day-of-week fields for specifying "no specific value".
The 'L' character, it is the short form of "last" that allows us for using the day-of -month and day-of-week fields. If we can use the value 'L' in the day-of-month field that means last day of the month like: 31 for January, 28 for February not the leap years. In case of day-of-week field that means "7 stands for SAT".
There are following example of expressions for specify the JavaDOC for CronTrigger:
1.Example: Write an expression to create a trigger that fires ever 10 minutes.
     "0  0/10  *  *  *  ?"
2.Example: Write an expression to create a trigger that fires every 10 minutes, at 10 seconds after the minute.
      "10  0/10  *  *  *  ?"
  (That means each firing after the 10 seconds interval like: 8:00:00am, 8:10:00am,8:20:00am etc.)
3.Example: Write an expression to create a trigger that fires at 9:30, 10:30, 11:30, 12:30 and 13:30 on every Sunday and Saturday.
      "0  30  9-13  ?  *  SUN, SAT"