Showing posts with label quartz scheduler. Show all posts
Showing posts with label quartz scheduler. Show all posts

Wednesday, August 10, 2011

Dynamically Creates Quartz Scheduler using Spring

 Here are  my requirements: I need to dynamically create quartz cron triggers and add them to my scheduler. All my triggers schedule the same job but with trigger specific job mapping data.

Before I show how to dynamically create quartz schedulers using Spring, let's quickly review Sping's static configuration. The following excerpts come from "Chapter 23 Scheduling and Tread Pooling" in Spring 2.5.6 reference document and shows how to statically create a quartz scheduler:

<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailBean">
   <property name="jobClass" value="example.ExampleJob"/>
</bean>

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
   <property name="jobDetail" ref="exampleJob" />
   <!-- run every morning at 6 AM -->
   <property name="cronExpression" value="0 0 6 * * ?" />
</bean>

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
   <property name="triggers">
      <list>
         <ref bean="cronTrigger" />
      </list>
   </property>
</bean>

Spring's JobDetailBean extends quartz's JobDetail and provides the following added functions:
  1. Setups sensible defaults in its life-cycle init method afterPropertiesSet() e.g. set the job's name to the bean name and set the job's group name to "DEFAULT" if they are empty (Remember that quartz scheduler uniquely identifies a job by its name and group);
  2. Supports any job class that implements the Runnable interface besides quartz'a regular Job interface;
  3. If your actual Job class extends Spring's QuartzJobBean, Spring also automatically injects the job map data to the job's properties;
Spring's CronTriggerBean extends quartz CronTrigger and provides the following added functions:
  1. Setups sensible defaults in its life-cycle init method afterPropertiesSet() e.g. set the trigger's name to the bean name and set the trigger's group name to "DEFAULT" if they are empty (Remember that quartz scheduler uniquely identifies a trigger by its name and group).
  2. Associates the trigger to the provided's JobDetail in afterPropertiesSet();
  3. Set the trigger's timezone to the default one and start time to the current time afterPropertiesSet();
  4. Sets trigger specific job map data through the Java's Map interface;
Spring's SchedulerFactorBean is a quartz Scheduler factory bean and wires jobs and triggers together. It implements many functions behind the scene that you have to do by yourself  if you directly use quartz API:
  1. Overrides default quartz properties e.g. thread pool parameters and jmx export;
  2. Registers triggers;
  3. Registers jobs. If you already assigned a job in your Spring trigger bean, you don't need this;
  4. Sets the JobFactory to the default Spring's AdaptableJobFactory if it is not set by you. AdaptableJobFactory support Item 2 in the above JobDetailBean's added functions. AdaptableJobFactory's sublass SpringBeanJobFactory also supports the same function as mentioned in Item 3 in the above JobDetailBean's added function.
  5. Schedules your jobs with quartz's scheduler;
All the above functions are implemented in the Spring' init method afterPropertiesSet().

Here are the steps to dynamically configure quartz scheduler using Spring to meet my above requirements.
  1. Still configures my JobDetailBean, and my actual Job also extends Spring's QuartzJobBean in order to take advantage of the added function;
  2. Still configures my CronTriggerBean using Spring  in order to take advantage of the added function. But its scope should be "prototype" so that I can dynamically retrieve a new trigger bean instance and set its cronExpression and its job map data.
    Here is the new trigger configuration:

    <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean" scope="prototype">
    <property name="jobDetail" ref="exampleJob"/>
    </bean>

    Here is the pseudo code:
    CronTrigger ct = (CronTrigger)appCtx.getBean("cronTrigger");
    ct.setName(ct.getName() + some-distinguisher);
    ct.getJobDataMap().put(some-key, some-value);
    ct.setCronExpression(some-exp); 

  3. Still configures my SchedulerFactorBean. But I need to add my above triggers to the scheduler. Because I create the above trigger after SchedulerFactorBean's afterPropertiesSet(), the trigger's associated JobDetail is not added to the scheduler. But I can explicitly register the JobDetail:

    <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false">
       <property name="jobDetails">
          <list>
            <ref bean="exampleJob"/>
          </list>
       </property>
       <property name="triggers"> <!-- will dynamically add more triggers -->
          <list/>
       </property>
       <property name="autoStartup" value="true"/>
       <property name="quartzProperties">
          <props>
            <prop key="org.quartz.scheduler.jmx.export">true</prop>
            <prop key="org.quartz.threadPool.threadCount">2</prop>
          </props>
       </property>
    </bean>
    Here is the pseudo code:
    Scheduler sched = (Scheduler)appCtx.getBean("scheduler");
    sched.scheduleJob(ct);

Wednesday, August 11, 2010

Quartz CronTrigger Scheduling Policy - Fixed-Rate or Fixed-Delay?

As you know the Java Timer class allows you to schedule task either as Fixed-Rate or Fixed-Delay.

In fixed-delay execution, each execution is scheduled relative to the actual execution time of the previous execution. If an execution is delayed for any reason (such as garbage collection or other background activity), subsequent executions will be delayed as well. In the long run, the frequency of execution will generally be slightly lower than the reciprocal of the specified period (assuming the system clock underlying Object.wait(long) is accurate).
In fixed-rate execution, each execution is scheduled relative to the scheduled execution time of the initial execution. If an execution is delayed for any reason (such as garbage collection or other background activity), two or more executions will occur in rapid succession to "catch up." In the long run, the frequency of execution will be exactly the reciprocal of the specified period (assuming the system clock underlying Object.wait(long) is accurate). 

Quartz supports Unix/Linux-like cron scheduling with its CronTrigger class which has a fixed-rate like execution policy.
For example if you configured your job to run every 5 seconds in a thread pool with more than 1 worker thread, the job will be kicked off every five 5 seconds.
If the previous job execution has not be done yet, the new job execution will pick up an available worker thread in the thread pool. This is different from Java's Timer which only has one single worker thread.

Quartz doesn't support Fixed-delay as of version 1.6. Actually you can find the details by digging into its source code.
The key classes and interfaces are its QuartzScdedulerThread, JobStore and CronTirgger.
In method run() of QuartzScdedulerThrea, you can find it calls triggerFired() before calling runInThread() to actually run the job.
triggerFired() will eventually lead to a call to triggered() in CronTrigger where nextFireTime is calculated.
So you know no matter how long your job run in runInThread(), the next execution time is always correctly calculated from its last execution time by method triggerFired().