Scheduling Jobs Using Quartz

The open source Quartz project is a dedicated job scheduling engine designed to be used in both J2EE and J2SE settings. Quartz provides a huge range of features such as persistent jobs, clustering, and distributed transactions. We do not look at the clustering or distributed transaction features here—you can find out more about these online at www.opensymphony.com/quartz. Spring's Quartz integration is very similar to Spring's Timer integration in that it provides for declarative configuration of jobs, triggers, and schedules. In addition to this, Spring provides additional job persistence features that allow the scheduling of a Quartz job to take part in a Spring-managed transaction.

Introducing Quartz

Quartz is an extremely powerful job scheduling engine, and we cannot hope to cover everything about it in the remainder of this chapter. However, we do cover the main aspects of Quartz that are related to Spring, and we discuss how you can use Quartz from a Spring application. As with our Timer discussion, we start by looking at Quartz separately from Spring, and then we look at Quartz/Spring integration.

The core of Quartz is made up of two interfaces, Job and Scheduler, and two classes, JobDetail and Trigger. From their names, it should be apparent what most of these do, but the role of the JobDetail class is not clear. Unlike Timer-based scheduling, tasks are not executed using a single instance of your job class; instead, Quartz creates instances as it needs them. You can use the JobDetail class to encapsulate the job state and to pass information to a job and between subsequent executions of a job. With Timer-based scheduling, there was no notion of a Trigger with Trigger logic encapsulated by the Timer class itself. Quartz supports a pluggable architecture for Triggers, which allows you to create your own implementations as you see fit. That said, you rarely create your own Trigger implementations because Quartz provides the super-powerful CronTrigger out of the box, which allows you to use cron expressions (more on that shortly) to have fine-grained control over job execution.

Simple Job Scheduling

To create a job for use in Quartz, simply create a class that implements the Job interface. The Job interface defines a single method, execute(), which is where you call your business logic from. Quartz passes an instance of JobExecutionContext context to the execute() method, allowing you to access context data about the current execution. We look at this in more detail in the next section.

Listing 14-14 show a simple Job implementation that writes "Hello World" to stdout.

Listing 14-14: Creating a Simple Job

image from book
package com.apress.prospring.ch14.quartz;      import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException;      public class HelloWorldJob implements Job {          public void execute(JobExecutionContext context)             throws JobExecutionException {         System.out.println("Hello World!");     }      } 
image from book

To schedule this job to run, we first need to obtain a Scheduler instance, then create a JobDetail bean that contains information about the job, and then create a Trigger to govern job execution. The code for this is shown in Listing 14-15.

Listing 14-15: Scheduling Jobs in Quartz

image from book
package com.apress.prospring.ch14.quartz;      import java.util.Date;      import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory;      public class HelloWorldScheduling {          public static void main(String[] args) throws Exception {              Scheduler scheduler = new StdSchedulerFactory().getScheduler();         scheduler.start();              JobDetail jobDetail = new JobDetail("helloWorldJob",                 Scheduler.DEFAULT_GROUP, HelloWorldJob.class);              Trigger trigger = new SimpleTrigger("simpleTrigger",                 Scheduler.DEFAULT_GROUP, new Date(), null,                 SimpleTrigger.REPEAT_INDEFINITELY, 3000);                  scheduler.scheduleJob(jobDetail, trigger);     } }
image from book

This code starts by obtaining an instance of Scheduler using the StdSchedulerFactory class. We are not going to look at this class in any detail here, but you can find out more information in the Quartz tutorial, which is available on the website. For now, it is enough to know that the StdSchedulerFactory.getScheduler() class returns a Scheduler instance that is ready to run. In Quartz, a Scheduler can be started, stopped, and paused. If a Scheduler has not been started or is paused, then no Triggers fire, so we start the Scheduler using the start() method.

Next, we create the JobDetail instance of the job we are scheduling, passing in three arguments to the constructor. The first argument is the job name and is used to refer to this job when using one of the Scheduler interface's administration methods, such as pauseJob(), which allows a particular job to be paused. The second argument is the group name for which we are using the default group name. Group names can be used to refer to a group of jobs together, perhaps to pause them all using Scheduler.pauseJobGroup(). You should note that job names are unique within a group. The third and final argument is the Class, which implements this particular job.

With the JobDetail instance created, we now move on to create a Trigger. In this example, we use the SimpleTrigger class, which provides JDK Timer-style trigger behavior. The first and second arguments passed to the SimpleTrigger constructor are trigger name and group name, respectively. Both of these arguments perform similar functions for a Trigger as they do for a JobDetail. Trigger names are also unique within a group. The third and fourth arguments, both of type Date, are the start and end date for this Trigger. By specifying null for the end date, we are saying there is no end date. The ability to specify an end date for a trigger is not available when you are using Timer. The next argument is the repeat count, which allows you to specify the maximum number of times the Trigger can fire. We use the constant REPEAT_INDEFINITELY to allow the Trigger to fire without a limit. The final argument is the interval between Trigger firings and is defined in milliseconds. We have defined an interval of 3 seconds.

The final step in this example is to schedule the job with a call to Scheduler.schedule() that passes in the JobDetail instance and the Trigger. If you run this application, you see the familiar stream of "Hello World!" messages appearing gradually in your console.

Using JobDetail Beans

In the previous example, all information for the job execution was contained in the job itself. However, you can pass state into the job using the JobDetail class. Each instance of JobDetail has an associated JobDataMap instance, which implements Map, and allows you to pass in job data in key/value pairs. Your jobs can modify data in the JobDataMap to allow for the passing of data between subsequent executions of the job. However, there are some considerations related to job persistence when using this approach. We discuss these later in the section entitled "About Job Persistence."

In Listing 14-16, you can see an example of a Job that uses data contained in the JobDataMap to perform its processing.

Listing 14-16: Using the JobDataMap

image from book
package com.apress.prospring.ch14.quartz;      import java.util.Map;      import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException;      public class MessageJob implements Job {          public void execute(JobExecutionContext context)             throws JobExecutionException {         Map properties = context.getJobDetail().getJobDataMap();              System.out.println("Previous Fire Time: "                 + context.getPreviousFireTime());         System.out.println("Current Fire Time: " + context.getFireTime());         System.out.println("Next Fire Time: " + context.getNextFireTime());              System.out.println(properties.get("message"));              System.out.println("");     } }
image from book

Here you can see that we access the JobDetail using the JobExecutionContext instance that is passed to the execute() method. Using this JobDetail instance, we can then obtain the JobDataMap and from there, we are able to extract the Object that is keyed as message and write it to stdout. Also notice that we are able to get information about the previous, current, and next execution of this job from the JobExecutionContext.

In Listing 14-17, you can see an example of how you populate the JobDataMap with data when scheduling the Job.

Listing 14-17: Adding Data to the JobDataMap

image from book
package com.apress.prospring.ch14.quartz;      import java.util.Date; import java.util.Map;      import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory;      public class MessageScheduling {          public static void main(String[] args) throws Exception{         Scheduler scheduler = new StdSchedulerFactory().getScheduler();         scheduler.start();              JobDetail jobDetail = new JobDetail("messageJob",                 Scheduler.DEFAULT_GROUP, MessageJob.class);                  Map map = jobDetail.getJobDataMap();         map.put("message", "This is a message from Quartz");              Trigger trigger = new SimpleTrigger("simpleTrigger",                 Scheduler.DEFAULT_GROUP, new Date(), null,                 SimpleTrigger.REPEAT_INDEFINITELY, 3000);                  scheduler.scheduleJob(jobDetail, trigger);     } } 
image from book

You will recognize much of this code from the previous example shown in Listing 14-15, but notice that once the JobDetail instance has been created, we access the JobDataMap and add it to a message, keyed as message. If you run this example and leave it running for a few iterations, you end up with output similar to this:

Previous Fire Time: null Current Fire Time: Tue Sep 28 17:16:48 BST 2005 Next Fire Time: Tue Sep 28 17:16:51 BST 2005 This is a message from Quartz      Previous Fire Time: Tue Sep 28 17:16:48 BST 2005 Current Fire Time: Tue Sep 28 17:16:51 BST 2005 Next Fire Time: Tue Sep 28 17:16:54 BST 2005 This is a message from Quartz      Previous Fire Time: Tue Sep 28 17:16:51 BST 2005 Current Fire Time: Tue Sep 28 17:16:54 BST 2005 Next Fire Time: Tue Sep 28 17:16:57 BST 2005 This is a message from Quartz

Here you can see that the message contained in the JobDataMap is written to stdout after the information about the execution times of the previous, current, and next execution is displayed. As you will see shortly, when using Spring to configure Quartz scheduling, you can create the JobDataMap in your Spring configuration file, allowing you to externalize all Job configuration completely.

Using the CronTrigger

In the previous examples, we used the SimpleTrigger class, which provides trigger functionality very similar to that of the JDK Timer class. However, where Quartz excels is in its support for complex trigger expressions using the CronTrigger. CronTrigger is based on the Unix cron daemon, a scheduling application that supports a simple, yet extremely powerful, trigger syntax. Using CronTrigger, you can quickly and accurately define trigger expressions that would be extremely difficult or impossible to do with the SimpleTrigger class. For instance, you can create a trigger that says, "fire every 5 seconds of every minute, starting at the third second of the minute, but only between the hours of 14:00 and 17:00" or "fire on the last Friday of every month."

A CronTrigger syntax expression, referred to as a cron expression, contains six required components and one optional component. A cron expression is written on a single line and each component is separated from the next by space. Only the last, or rightmost, component is optional. Table 14-2 describes the cron components in detail.

Table 14-2: Components of a Cron Expression

Position

Meaning

Allowed Special Characters

1

Seconds (0–59)

,, -, *, /

2

Minutes (0–59)

,, -, *, /

3

Hours (0–23)

,, -, *, /

4

Day of month (1–31)

,, -, *, /, ?, L, C

5

Month

(either JAN–DEC or 1–12),, -, *, /

6

Day of week (either SUN–SAT or 1–7)

,, -, *, /, ?, L, C, #

7

Year (optional, 1970–2099), when empty, full range is assumed

,, -, *, /

Each component accepts the typical range of values that you would expect, such as 0–59 for seconds and minutes and 1–31 for day of the month. For the month and day of the week components, you can use numbers, such as 1–7 for day of the week, or text such as SUN–SAT.

Each field also accepts a given set of special symbols, so placing a * in the hours component means every hour, and using an expression such as 6L in the day-of-the-week component means last Friday of the month. Table 14-3 describes cron wildcards and special symbols in detail.

Table 14-3: Cron Expression Wildcards and Special Symbols

Special Character

Description

*

Any value. This special character can be used in any field to indicate that the value should not be checked. Therefore, our example cron expression will be fired on any day of the month, any month, and any day of the week between 1970 and 2099.

?

No specific value. This special character is usually used with other specific values to indicate that a value must be present but will not be checked.

-

Range. For example 10–12 in the Hours field means hours 10, 11, and 12.

,

List separator. Allows you to specify a list of values, such as MON, TUE, WED in the Day of week field.

/

Increments. This character specifies increments of a value. For example 0/1 in the Minute field in our example means every 1-minute increment of the minute field, starting from 0.

L

L is an abbreviation for Last. The meaning is a bit different in Day of month and Day of week. When used in the Day of month field, it means the last day of the month (31st of March, 28th or 29th of February, and so on). When used in Day of week, it has the same value as 7—Saturday. The L special character is most useful when you use it with a specific Day of week value. For example, 6L in the Day of week field means the last Friday of each month.

#

This value is allowed only for the Day of week field and it specifies the nth day in a month. For example 1#2 means the first Monday of each month.

C[*]

The Calendar value is allowed for the Day of month and Day of week fields. The values of days are calculated against a specified calendar. Specifying 20C in the Day of month field fires the trigger on the first day included in the calendar on or after the 20th. Specifying 6C in the Day of week field is interpreted as the first day included in the calendar on or after Friday.

[*]C At the time of writing, support for the C special character and specifying both Day of week and Day of month values has not been not completed.

The last thing to bear in mind when writing cron expressions is daylight saving time changes. This may cause a trigger to fire twice in the spring or not to fire at all in the fall (sorry, southern hemisphere).

There are many more permutations for cron expressions than we can discuss here and you can find a detailed description of cron syntax in the JavaDoc for the CronTrigger class.

Listing 14-18 shows an example of the CronTrigger class in action.

Listing 14-18: Using the CronTrigger Class

image from book
package com.apress.prospring.ch14.quartz;      import java.util.Map;      import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory;      public class CronTriggerExample {          public static void main(String[] args) throws Exception {         Scheduler scheduler = new StdSchedulerFactory().getScheduler();         scheduler.start();              JobDetail jobDetail = new JobDetail("messageJob",                 Scheduler.DEFAULT_GROUP, MessageJob.class);              Map map = jobDetail.getJobDataMap();         map.put("message", "This is a message from Quartz");              String cronExpression = "3/5 * 14,15,16,17 * * ?";              Trigger trigger = new CronTrigger("cronTrigger",                 Scheduler.DEFAULT_GROUP, cronExpression);              scheduler.scheduleJob(jobDetail, trigger);     } }
image from book

Much of this code should look familiar to you; the only major difference here is that we use the cron expression. The actual creation of the CronTrigger class is very similar to the creation of the SimpleTrigger class in that you have a name and a group name. To help you understand the cron expression in the example, we break it down into components. The first component, 3/5, means every 5 seconds starting at the third second of the minute. The second component, *, simply says every minute. The third component, 14, 15, 16, 17, restricts this trigger to running between 14:00 and 17:59—that is, when the time begins with 14, 15, 16, or 17. The next two components are both wildcards saying that this trigger can run in any month or any year.

The final component uses the wildcard, ?, to indicate that this trigger can run on any day of the week. This expression has the net effect of firing every 5 seconds, starting on the third second of the minute, but only between 14:00 and 17:59.

If you run this example, depending on the time of day, you either see a blank screen or the ever increasing list of "Hello World"s. Try modifying the first component in the expression to change the frequency or at which second in the minute the trigger starts. You should also try modifying other components to see what effects you get.

The CronTrigger is great for almost all trigger requirements; however, expressions can quickly get convoluted when you need to consider exceptions to the rule. For instance, consider a process that checks a task list for a user every Monday, Wednesday, and Friday at 11:00 and 15:00. Now consider what happens when you want to prevent this trigger from firing when the user is on vacation. Thankfully, Quartz provides support for this via the Calendar interface. Using the Calendar interface, you can accurately define a period that should either be explicitly included or explicitly excluded from a trigger's normal schedule. Quartz comes with six implementations of Calendar, one of which is the HolidayCalendar that stores a list of days to be excluded from a trigger's schedule. Listing 14-19 shows a modification of the previous example that uses a HolidayCalendar to exclude December 25, 2005.

Listing 14-19: Explicitly Excluding Dates with HolidayCalendar

image from book
package com.apress.prospring.ch14.quartz;      import java.util.Calendar; import java.util.Map;      import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.calendar.HolidayCalendar;      public class CronWithCalendarExample {          public static void main(String[] args) throws Exception {         Scheduler scheduler = new StdSchedulerFactory().getScheduler();         scheduler.start();                  // create a calendar to exclude a particular date         Calendar cal = Calendar.getInstance();         cal.set(2005, Calendar.DECEMBER, 25);                  HolidayCalendar calendar = new HolidayCalendar();         calendar.addExcludedDate(cal.getTime());                  // add to scheduler         scheduler.addCalendar("xmasCalendar", calendar, true, false);                  JobDetail jobDetail = new JobDetail("messageJob",                 Scheduler.DEFAULT_GROUP, MessageJob.class);              Map map = jobDetail.getJobDataMap();         map.put("message", "This is a message from Quartz");              String cronExpression = "3/5 * 14,15,16,17 * * ?";              Trigger trigger = new CronTrigger("cronTrigger",                 Scheduler.DEFAULT_GROUP, cronExpression);                  trigger.setCalendarName("xmasCalendar");              scheduler.scheduleJob(jobDetail, trigger);     } }
image from book

Here you can see that we create an instance of HolidayCalendar and then, using the addExcludedDate() method, we exclude the 25th of December. With the Calendar instance created, we add the Calendar to the Scheduler using the addCalendar() method, giving it a name of xmasCalendar. Then later, before adding the CronTrigger, we associate it with the xmasCalendar. Using this approach saves you from having to create complex cron expressions just to exclude a few arbitrary dates.

About Job Persistence

Quartz provides support for Job persistence, allowing you to add jobs at runtime or make changes to existing jobs and have these changes and additions persist for subsequent executions of the Job. Central to this concept is the JobStore interface, implementations of which are used by Quartz when it is performing persistence. By default, Quartz uses the RAMJobStore implementation, which simply stores Jobs in memory. Other available implementations are JobStoreCMT and JobStoreTX. Both of these classes persist job details using a configured DataSource and support the creation and modification of Jobs as part of a transaction. The JobStoreCMT implementation is intended to be used in an application server environment and takes part in container-managed transactions. For stand-alone applications you should use the JobStoreTX implementation. Spring provides its own LocalDataSourceJobStore implementation of JobStore, which can take part in Spring-managed transactions. We take a look at this implementation when we discuss Spring support for Quartz.

Earlier on, you saw how you can modify the contents of the JobDataMap to pass information between different executions of the same Job. However, if you try to run that example using a JobStore implementation other the RAMJobStore, you will be surprised to see that it doesn't work. The reason for this is that Quartz supports the notion of stateless and stateful jobs. When using the RAMJobStore and modifying the JobDataMap, you are actually modifying the store directly, so the type of Job is unimportant, but this is not the case when you are using implementations other than RAMJobStore. A stateless Job only has the data in the JobDataMap persisted when it is added to the Scheduler, whereas stateful Jobs have their JobDataMap persisted after every execution. To mark a Job as stateful, implement the StatefulJob interface instead of the Job interface. StatefulJob is a subinterface of Job, so you do not need to implement Job as well. You should also be aware that any data you place in the JobDataMap when using Job persistence must be serializable, because Quartz writes the JobDataMap as a serialized blob to the database.

Quartz Support in Spring

Spring's Quartz integration follows a similar pattern to the integration with Timer in that it allows you to configure your job scheduling fully within the Spring configuration file. In addition to this, Spring provides further classes to integrate with the Quartz JobStore, thus allowing you to configure Job persistence in your configuration and for Job modification to take part in Spring-managed transactions.

Scheduling a Job with Spring

As you would expect, much of the code you need to schedule a Quartz Job using Spring goes into the Spring configuration file. Indeed, you only need to load the ApplicationContext in your application for the configuration to take effect and for Spring to start the Scheduler automatically.

In Listing 14-20, you can see the configuration code required to configure the MessageJob class you saw in Listing 14-16 to run once every 3 seconds.

Listing 14-20: Configuring Scheduling Declaratively

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean  >         <property name="jobClass">             <value>com.apress.prospring.ch14.quartz.MessageJob</value>         </property>         <property name="jobDataAsMap">             <map>                 <entry key="message">                     <value>This is a message from the Spring configuration file!                    </value>                 </entry>             </map>         </property>     </bean>          <bean                   >         <property name="jobDetail">             <ref local="job"/>         </property>         <property name="startDelay">             <value>1000</value>         </property>         <property name="repeatInterval">             <value>3000</value>         </property>     </bean>          <bean                  >         <property name="triggers">             <list>                 <ref local="trigger"/>             </list>         </property>     </bean> </beans>
image from book

Here you can see that we use the JobDetailBean class, which extends the JobDetail class, to configure the job data in a declarative manner. The JobDetailBean provides more JavaBeans- style properties that are accessible by Spring, and it also provides sensible defaults for properties that you usually have to specify yourself. For instance, notice that we did not specify a job name or a group name. By default, the JobDetailBean uses the ID of the <bean> tag as the job name and the default group of the Scheduler as the group name. Notice that we are able to add data to the JobDataMap property using the jobDataAsMap property. The name of this property is not a typo—you can't add directly to the jobDataMap property because it is of type JobDataMap and this type is not supported in Spring configuration files.

With the JobDetailBean configured, the next step is to create a trigger. Spring offers two classes, SimpleTriggerBean and CronTriggerBean, that wrap the SimpleTrigger and CronTrigger classes, allowing you to configure them declaratively and to associate them with a JobDetail- Bean—all within your configuration file. Notice that in the example above, shown in Listing 14-20, we defined a starting delay of 1 second and then a repeat interval of 3 seconds. By default, the SimpleTriggerBean sets the repeat count to infinite.

The final piece of configuration you need is for the SchedulerFactoryBean. By default, SchedulerFactoryBean creates an instance of StdSchedulerFactory from which to create the Scheduler implementation. You can override this behavior by setting the schedulerFactoryClass property to the name of a Class that implements SchedulerFactory, which you wish to use in place of StdSchedulerFactory. The only property that you need to configure scheduling is the triggers property, which accepts a List of TriggerBeans.

Because all of the job scheduling configuration is contained in the configuration, you need very little code to actually start the Scheduler and have the Jobs execute. In fact, all you need to do is create the ApplicationContext, as shown in Listing 14-21.

Listing 14-21: Testing Declarative Quartz Configuration

image from book
package com.apress.prospring.ch14.quartz.spring;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;      public class SimpleSpringQuartzIntegrationExample {          public static void main(String[] args) {         ApplicationContext ctx = new FileSystemXmlApplicationContext(         "./ch14/src/conf/quartzSimple.xml");     } }
image from book

As you can see, this class does nothing more than create an instance of ApplicationContext using the configuration shown in Listing 14-20. If you run this application and leave it running for a few iterations, you end up with something like this:

Previous Fire Time: null Current Fire Time: Wed Sep 29 14:59:08 BST 2005 Next Fire Time: Wed Sep 29 14:59:11 BST 2005 This is a message from the Spring configuration file!      Previous Fire Time: Wed Sep 29 14:59:08 BST 2005 Current Fire Time: Wed Sep 29 14:59:11 BST 2005 Next Fire Time: Wed Sep 29 14:59:14 BST 2005 This is a message from the Spring configuration file!      Previous Fire Time: Wed Sep 29 14:59:11 BST 2005 Current Fire Time: Wed Sep 29 14:59:14 BST 2005 Next Fire Time: Wed Sep 29 14:59:17 BST 2005 This is a message from the Spring configuration file!

Notice that it is running just like it was for the previous MessageJob example, but the message displayed is the message configured in the Spring configuration file.

Using Persistent Jobs

One of the great features of Quartz is its ability to create stateful, persistent Jobs. This opens up some great functionality that is not available when you are using Timer-based scheduling. With persistent Jobs, you can add Jobs to Quartz at runtime and they will still be in your application after a restart. Plus, you can modify the JobDataMap passed between executions of a Job and changes will still be in effect after a restart.

In this example, we are going to schedule two jobs, one using Spring configuration mechanisms, and one at runtime. We then see how the Quartz persistence mechanism copes with changes to the JobDataMap for these Jobs and what happens in subsequent executions of the application.

To start with, you need to create a database in which Quartz can store the Job information. In the Quartz distribution—we used version 1.4.2—you will find a selection of database scripts for a variety of different RDMBS flavors. For the example here, we use MySQL, but you should not encounter problems using a different database as long as Quartz has a database script for it. Once you have located the script for your database, execute it against your database and verify that 12 tables, each with the prefix qrtz, have been created.

Next, create your test Job. Because we want to make changes to JobDataMap during Job execution, we need to flag to Quartz that it should treat this as a stateful Job. We do this by implementing the StatefulJob interface rather than the Job interface. This is shown in Listing 14-22.

Listing 14-22: Creating a Stateful Job

image from book
package com.apress.prospring.ch14.quartz.spring;      import java.util.Map;      import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.StatefulJob;      public class PersistentJob implements StatefulJob {          public void execute(JobExecutionContext context)             throws JobExecutionException {         Map map = context.getJobDetail().getJobDataMap();         System.out.println("[" + context.getJobDetail().getName() + "]"                 + map.get("message"));         map.put("message", "Updated Message");     }      }
image from book

The StatefulJob interface does not add additional methods for your class to implement; it is simply a marker telling Quartz that it should persist the JobDetail after every execution. Here you can see that we display the message that is stored in the JobDataMap along with the name of the Job.

The next step is to configure the Job in Spring and configure the Scheduler with a DataSource it can use for persistence. This is shown in Listing 14-23.

Listing 14-23: Configuring Quartz Persistence in Spring

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean  >         <property name="jobClass">             <value>com.apress.prospring.ch14.quartz.spring.PersistentJob</value>         </property>         <property name="jobDataAsMap">             <map>                 <entry key="message">                     <value>Original Message</value>                 </entry>             </map>         </property>     </bean>     <bean               >         <property name="driverClassName">             <value>com.mysql.jdbc.Driver</value>         </property>         <property name="url">             <value>jdbc:mysql://localhost:3306/quartz</value>         </property>         <property name="username"><value>root</value></property>         <property name="password"><value></value></property>     </bean>     <bean                   >         <property name="jobDetail">             <ref local="job"/>         </property>         <property name="startDelay">             <value>1000</value>         </property>         <property name="repeatInterval">             <value>3000</value>         </property>     </bean>     <bean                  >         <property name="triggers">             <list>                 <ref local="trigger"/>             </list>         </property>         <property name="dataSource">             <ref local="dataSource"/>         </property>     </bean> </beans>
image from book

You will recognize much of this configuration code from Listing 14-20; the important part here is the dataSource bean. In this code, we use the Spring class SingleConnectionDataSource; this is a useful DataSource implementation for testing but never use it in production. And remember, you need to modify the connection details in the configuration as is appropriate for your environment. For more details on configuring other DataSources with Spring, see Chapter 8. Using the configured dataSource bean, we set the dataSource property of the SchedulerFactoryBean. By doing this, we instruct Spring to create a Scheduler that is configured to persist Job data using the given DataSource. Internally, this is achieved using Spring's own JobStore implementation, LocalDataSourceJobStore.

With the configuration complete, all that remains is to load it in an application and add another Job to the Scheduler at runtime. Listing 14-24 shows the code for this.

Listing 14-24: Testing Job Persistence

image from book
package com.apress.prospring.ch14.quartz.spring;      import java.util.Date;      import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;      public class SpringWithJobPersistence {          public static void main(String[] args) throws Exception {         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 "./ch14/src/conf/quartzPersistent.xml");              // get the scheduler         Scheduler scheduler = (Scheduler) ctx.getBean("schedulerFactory");              JobDetail job = scheduler.getJobDetail("otherJob",                 Scheduler.DEFAULT_GROUP);              if (job == null) {             // the job has not yet been created             job = (JobDetail) ctx.getBean("job");             job.setName("otherJob");             job.getJobDataMap().put("message", "This is another message");                  Trigger trigger = new SimpleTrigger("simpleTrigger",                     Scheduler.DEFAULT_GROUP, new Date(), null,                     SimpleTrigger.REPEAT_INDEFINITELY, 3000);                  scheduler.scheduleJob(job, trigger);         }     } }
image from book

This code requires little explanation; however, note that before we schedule the second job, we check to see if it already exists using the Scheduler.getJobDetail() method. This is so we do not overwrite the Job on subsequent runs of the application.

The first time you run this example, you get output something like this:

[otherJob]This is another message [job]Original Message [otherJob]Updated Message [job]Updated Message [otherJob]Updated Message [job]Updated Message

As you can see, the first time each Job executes, the message displayed is the original message that was configured in the JobDataMap when the Job was scheduled. On subsequent executions, each Job displays the updated message that was set during the previous execution. If you stop the application and then restart it, you see something slightly different:

[otherJob]Updated Message [job]Updated Message [otherJob]Updated Message [job]Updated Message [otherJob]Updated Message [job]Updated Message

This time you can see that because the Job data was persisted, you do not need to re-create the second Job and the JobDataMap accurately reflects changes that were made during the last run of the application.

Scheduling Arbitrary Jobs with Quartz

Like the Timer-based scheduling classes, Spring provides the ability to schedule the execution of arbitrary methods using Quartz. We are not going to go into any detail on this because it works in an almost identical manner to the Timer approach. Instead of using MethodInvokingTimerTaskFactoryBean, you use MethodInvokingJobDetailFactoryBean, and instead of creating TimerTasks automatically, you create JobDetails automatically.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net