Just a small post about something I noticed in Quartz. I wanted to offer the user a screen with functionality so they could (re)schedule a batch job which then would do all kinds of batch stuf. To schedule the job in my application I used the following Java code:

JAVA:
  1. public void resetScheduleJob() throws CustomSystemException {
  2.  
  3.       StdScheduler scheduler = (StdScheduler)MyQueueContext.getContext().getBean("scheduler");
  4.  
  5.       CronTriggerBean trigger = (CronTriggerBean)MyQueueContext.getContext().getBean("batchProducerTrigger");
  6.  
  7.       try {
  8.            trigger.setCronExpression(getCronExpression());
  9.  
  10.            scheduler.scheduleJob(trigger.getJobDetail(), trigger);
  11.  
  12.       } catch (ParseException pe) {
  13.          throw new CustomSystemException (pe);
  14.       } catch (SchedulerException se) {
  15.          throw new CustomSystemException (se);
  16.       }
  17.       LOG.info("Next run will be at: " + trigger.getNextFireTime());
  18.    }
  19. }
  20. private String getCronExpression() {
  21.  
  22.       // Small trick to get the hours of the day correct.
  23.       String hours = null;
  24.       if (startTime> endTime) {
  25.          hours = endTime + "-23,0-" + startTime;
  26.       } else if (startTime <endTime) {
  27.          hours = startTime + "-" + endTime;
  28.       } else {
  29.          hours = "0";
  30.       }
  31.       return "0 0/" + interval + " " + hours + " ? * " + days;
  32.    }


So this code runs fine the first time when it is called. But when you call it the second time (after the job exists) you will get:

org.quartz.ObjectAlreadyExistsException: Unable to store Job with name: 'batchProducerJob' and group: 'DEFAULT', because one already exists with this identification.

So I rewrote my code to:

JAVA:
  1. public void resetScheduleJob() throws CustomSystemException {
  2.       StdScheduler scheduler = (StdScheduler)MyQueueContext.getContext().getBean("scheduler");
  3.  
  4.       CronTriggerBean trigger = (CronTriggerBean)MyQueueContext.getContext().getBean("batchProducerTrigger");
  5.  
  6.       try {
  7.          trigger.setCronExpression(getCronExpression());
  8.  
  9.          scheduler.rescheduleJob(job.getName(), job.getGroup(), trigger);
  10.  
  11.          scheduler.start();
  12.  
  13.       } catch (ParseException pe) {
  14.          throw new CustomSystemException (pe);
  15.       } catch (SchedulerException se) {
  16.          throw new CustomSystemException (se);
  17.       }
  18.       LOG.info("Next run will be at: " + trigger.getNextFireTime());
  19.    }

Now this code won't fail if you call it several times..... Actually, it doesn't do anything! I would expect an exception if the scheduler must have an existing job before it can reschedule one. But nothing there, I only found out that my job wasn't started at all. So the trick is to check if there is a job with the same name, and if one is found, do the reschedule, otherwise the 'normal' schedule. Since I wasn't expecting this behaviour I thought may be there are more persons running into this so here is the solution:

JAVA:
  1. public void resetScheduleJob() throws CustomSystemException {
  2.       StdScheduler scheduler = (StdScheduler)MyQueueContext.getContext().getBean("scheduler");
  3.  
  4.       CronTriggerBean trigger = (CronTriggerBean)MyQueueContext.getContext().getBean("batchProducerTrigger");
  5.  
  6.       try {
  7.          trigger.setCronExpression(getCronExpression());
  8.  
  9.          JobDetail job = scheduler.getJobDetail(trigger.getJobDetail().getName(), trigger.getJobDetail().getGroup());
  10.          LOG.debug("Job found = " + job);
  11.  
  12.          if (job == null) {
  13.            scheduler.scheduleJob(trigger.getJobDetail(), trigger);
  14.          } else {
  15.             scheduler.rescheduleJob(job.getName(), job.getGroup(), trigger);
  16.          }
  17.  
  18.          scheduler.start();
  19.  
  20.       } catch (ParseException pe) {
  21.          throw new CustomSystemException (pe);
  22.       } catch (SchedulerException se) {
  23.          throw new CustomSystemException (se);
  24.       }
  25.       LOG.info("Next run will be at: " + trigger.getNextFireTime());
  26.    }

And to make this example complete here is my Spring configuration:

XML:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE beans SYSTEM "../dtd/spring-beans.dtd">
  3. <beans>
  4.     <bean id="batchProducerJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
  5.       <property name="targetObject" ref="batchProducer" />
  6.       <property name="targetMethod" value="start" />
  7.     </bean>
  8.     <bean id="batchProducerTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
  9.         <property name="jobDetail" ref="batchProducerJob" />
  10.         </bean>
  11.     <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  12.            <!-- intentionally left empty -->
  13.     </bean>
  14.         <bean id="batchProducerScheduler" class="net.pascalalma.batch.schedulers.BatchProducerScheduler">
  15.         <property name="startTime" value="${batch.produce.starthour}" />
  16.         <property name="endTime" value="${batch.produce.endhour}" />
  17.         <property name="interval" value="${batch.produce.interval}" />
  18.         <property name="days" value="${batch.produce.days}" />
  19.        </bean>
  20. </beans>