Wednesday, 23 April 2014

Tracking Exceptions - Part 5 - Scheduling With Spring

It seems that I'm finally getting close to the end of this series of blogs on Error Tracking using Spring and for those who haven’t read any blogs in the series I’m writing a simple, but almost industrial strength, Spring application that scans for exceptions in log files and then generates a report. From the first blog in the series, these were my initial requirements:
  1. Search a given directory and its sub-directories (possibly) looking for files of a particular type.
  2. If a file is found then check its date: does it need to be searched for errors?
  3. If the file is young enough to be checked then validate it, looking for exceptions.
  4. If it contains exceptions, are they the ones we’re looking for or have they been excluded?
  5. If it contains the kind of exceptions we’re after, then add the details to a report.
  6. When all the files have been checked, format the report ready for publishing.
  7. Publish the report using email or some other technique.
  8. The whole thing will run at a given time every day
This blog takes a look at meeting requirement number 8: "The whole thing will run at a given time every day" and this means implementing some kind of scheduling.

Now, Java has been around for what seems like a very long time, which means that there are a number of ways of scheduling a task. These range from:
  • Using a simple thread with a long sleep(...).
  • Using Timer and TimerTask objects.
  • Using a ScheduledExecutorService.
  • Using Spring’s TaskExecutor and TaskScheduler classes.
  • Using Spring’s @EnableScheduling and @Scheduled annotations (Spring 3.1 onwards).
  • Using a more professional schedular.

The more professional variety of schedulers range from Quartz (free) to Obsidian (seemingly much more advanced, but costs money). Spring, as you might expect, includes Quartz Scheduler support; in fact there are two ways of integrating the Quartz Scheduler into your Spring app and these are:
  1. Using a JobDetailBean
  2. Using a MethodInvokingJobDetailFactoryBean.

For this application, I’m using the Spring’s Quartz integration together with a MethodInvokingJobDetailFactoryBean; the reason is that using Quartz allows me to configure my schedule using a a cron expression and MethodInvokingJobDetailFactoryBean can be configured quickly and simply using a few lines of XML.

The cron expression technique used by Spring and Quartz has been shamelessly taken from Unix’s cron scheduler. For more information on how Quartz deals with cron expressions, take a look at the Quartz cron page. If you need help in creating your own cron expressions then you’ll find that Cron Maker is a really useful utility.

The first thing to do when setting up Spring and Quartz is to include the following dependencies to your POM project file:

          <!-- QuartzJobBean is in spring-context-support.jar -->
          <dependency>
               <groupId>org.springframework</groupId>
               <artifactId>spring-context-support</artifactId>
               <version>${org.springframework-version}</version>
               <exclusions>
                    <!-- Exclude Commons Logging in favour of SLF4j -->
                    <exclusion>
                         <groupId>commons-logging</groupId>
                         <artifactId>commons-logging</artifactId>
                    </exclusion>
               </exclusions>
          </dependency>
          <!-- Spring + Quartz need transactions -->
          <dependency>
               <groupId>org.springframework</groupId>
               <artifactId>spring-tx</artifactId>
               <version>${org.springframework-version}</version>
          </dependency>
          <!-- Quartz framework -->
          <dependency>
               <groupId>org.quartz-scheduler</groupId>
               <artifactId>quartz</artifactId>
               <version>1.8.6</version>
               <!-- You can't use Quartz two with Spring 3 -->
          </dependency>

This is fairly straight forward with one tiny ’Gotcha’ at the end. Firstly Spring’s Quartz support is located in the spring-context-support-3.2.7.RELEASE.jar (substitute your Spring version number as applicable). Secondly, you also need to include the Spring transaction library - spring-td-3.2.7.RELEASE.jar. Lastly, you need to include a version of the Quartz scheduler; however, be careful as Spring 3.x and Quartz 2.x do not work together "out of the box" (although if you look around there are ad-hoc fixes to be found). I've used Quartz version 1.8.6, which does exactly what I need it to do.

The next thing to do is to sort out the XML configuration and this involves three steps:

  1. Create an instance of a MethodInvokingJobDetailFactoryBean. This has two properties: the name of the bean that you want to call at a scheduled interval and the name of the method on that bean that you want to invoke.
  2. Couple the MethodInvokingJobDetailFactoryBean to a cron expression using a CronTriggerFactoryBean
  3. Finally, schedule the whole caboodle using a SchedulerFactoryBean
Having configured these three beans, you get some XML that looks something like this:

     <bean id="FileLocatorJob"
          class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">

          <property name="targetObject" ref="errorTrackService" />
          <property name="targetMethod" value="trackErrors" />

     </bean>

     <bean id="FileLocatorTrigger"
          class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
          <property name="jobDetail" ref="FileLocatorJob" />
          <!-- run every morning at 2 AM -->
          <property name="cronExpression" value="${cron.expression}" />
     </bean>

     <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
          <property name="triggers">
               <list>
                    <ref bean="FileLocatorTrigger" />
                    <!-- Add other triggers for other jobs (if any) here <ref bean="" /> -->
               </list>
          </property>
     </bean>

Note that I’ve use a place-holder for my cron expression. The actual cron expression can be found in the app.properties file:

# run every morning at 2 AM 
cron.expression=0 0 2 * * ?

# Use this to test the app (every minute) 
#cron.expression=0 0/1 * * * ?

Here, I’ve got two expressions: one that schedules the job to run at 2AM every morning and another, commented out, that runs the job every minute. This is an instance of the app not quite being industrial strength. If there were a 'proper' app then I'd probably be using a different set of properties in every environment (DEV, UAT and production etc.).

There are only a couple of steps left before this app can be released and the first one of these is creating an executable JAR file. More on that next time.


The code for this blog is available on Github at: https://github.com/roghughe/captaindebug/tree/master/error-track. If you want to look at other blogs in this series take a look here...
  1. Tracking Application Exceptions With Spring
  2. Tracking Exceptions With Spring - Part 2 - Delegate Pattern
  3. Error Tracking Reports - Part 3 - Strategy and Package Private
  4. Tracking Exceptions - Part 4 - Spring's Mail Sender


1 comment:

Unknown said...

Obsidian is free if you use one node (no clustering).
You're right though about Obsidian being more advanced than Quartz.