Saturday, March 31, 2007

Using Quartz Scheduler in a Java EE Web Application

At times, you may have wanted to perform some action periodically in your web application. Quartz is an enterprise grade scheduler which can be used for such a task. Read here for the complete list of features of Quartz. For using the Quartz scheduler library in a Java EE web application, following needs to be done:
  • Include the quartz jars (quartz-all.jar and others in the lib path). In my case, some of the commons-xxx.jar files were already included in the project due to the dependency of another library (displaytag) on those jar files. So in my quartz setup i had to disinclude them. In the lib/build path, i only included, jta.jar and also everything which was not already there in the project from lib/optional path too (they are not many anyway).
  • We then had to create the tables required by quartz for storing job details and triggers across restart of application. This is an optional feature but an important one (which made us decide to use quartz in the first place over the JDK Timer). We used the docs/dbTables/tables_mysql.sql script to create the tables.
  • Then we copied (example_quartz.properties), modified and saved the quartz.properties file in the project and changed the packaging settings to include the properties file in the WEB-INF/classes path in the IDE. In the properties file, we changed the configuration to have quartz point to the data store we created in step 2.
# Configuring Main Scheduler Properties
org.quartz.scheduler.instanceName = MyScheduler
org.quartz.scheduler.instanceId = 1
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false

# Configuring ThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 30
org.quartz.threadPool.threadPriority = 5

# Configuring JobStore
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.dataSource = quartzDS
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = false

# Configuring datasource
org.quartz.dataSource.quartzDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.quartzDS.URL = jdbc:mysql://localhost:3306/mydb
org.quartz.dataSource.quartzDS.user = me
org.quartz.dataSource.quartzDS.password = secret
org.quartz.dataSource.quartzDS.maxConnections = 31

# Rest of config was retained from example_quartz.properties.
  • We added following lines to our web.xml:
<servlet>
<description>Quartz Initializer Servlet</description>
<servlet-name>QuartzInitializer</servlet-name>
<servlet-class>org.quartz.ee.servlet.QuartzInitializerServlet</servlet-class>
<init-param>
<param-name>shutdown-on-unload</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>start-scheduler-on-load</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>


This sets up the initializer servlet which can initialize the default scheduler and start the scheduler at application bootstrap time.
  • Now in our webservice/jsp/servlet of our web application we do the following:
try {
// A. Get the default scheduler.
Scheduler sched = StdSchedulerFactory.getDefaultScheduler();

// B.Generate a unique name identifier for jobs and
// triggers of your application as required.
// One way is to use hashCode() if its a string param.
String dataToPass = "someParamToPassToJob";
int id = dataToPass.hashCode();

// C. Create/Replace a poll job and add it to the scheduler.
JobDetail job =
new JobDetail("job_"+id, "SomeJobGroup", com.mycompany.MyJob.class);
job.setRequestsRecovery(true);
// Pass data to the poll job.
job.getJobDataMap().put("param", dataToPass);
sched.addJob(job, true);

// D. Create a Trigger with unique name
SimpleTrigger trigger = new SimpleTrigger("trig_"+id, "SomeTriggerGroup");


// E. Check if a trigger is already associated with this job
// This step is optional and depends on your application's requirement.

Trigger[] jobTriggers;
jobTriggers = sched.getTriggersOfJob("job_"+id, "SomeJobGroup");

boolean isTriggerAlreadyAssociated = false;
for (Trigger trig : jobTriggers) {
if (trig.getName().equals("trig_"+id) &&
trig.getGroup().equals("SomeTriggerGroup")) {
// the job already has this trigger associated with it
isTriggerAlreadyAssociated = true;
}
}

// F. Associate this trigger with the job
trigger.setJobName(job.getName());
trigger.setJobGroup(job.getGroup());

// G. Initialize the trigger with duration and resolution to fire
trigger.setStartTime(startTime.getTime());
trigger.setEndTime(endTime.getTime());
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
trigger.setRepeatInterval(repeatInterval); //in milliseconds

if (isTriggerAlreadyAssociated) {
// Reschedule the job with the existing trigger.
sched.rescheduleJob("trig_"+id, "SomeTriggerGroup", trigger);
} else {
// Schedule the job with the new trigger.
sched.scheduleJob(trigger);
}
} catch (SchedulerException se) {

}
  • Of course, the last thing is to write the Job class which does the actual work. The following is code from examples of Quartz.
public class PrintPropsJob implements Job {

public PrintPropsJob() {
}

public void execute(JobExecutionContext context)
throws JobExecutionException {

JobDataMap data = context.getJobDetail().getJobDataMap();
System.out.println("someProp = " + data.getString("someProp"));
System.out.println("someObjectProp = " + data.getObject("someObjectProp"));
}
}
  • Some important points to note about jobs and triggers:
  1. Jobs have a name and group associated with them, which should uniquely identify them within a single Scheduler.
  2. A Job can be associated with multiple triggers.
  3. Triggers are the 'mechanism' by which Jobs are scheduled.
  4. Many Triggers can point to the same Job, but a single Trigger can only point to one Job.
  5. JobDataMap holds state information for Job instances. JobDataMap instances are stored once when the Job is added to a scheduler. They are also re-persisted after every execution of StatefulJob instances.
  6. JobDataMap instances can also be stored with a Trigger. This can be useful in the case where you have a Job that is stored in the scheduler for regular/repeated use by multiple Triggers, yet with each independent triggering, you want to supply the Job with different data inputs.
  7. The JobExecutionContext passed to a Job at execution time also contains a convenience JobDataMap that is the result of merging the contents of the trigger's JobDataMap (if any) over the Job's JobDataMap (if any).
  8. We can have different job types:
  • Stateful Jobs - where state passed to job (in JobDataMap) is remembered (like static values) across executions of the job. Also, stateful jobs are not allowed to execute concurrently, which means new triggers that occur before the completion of the execute(xx) method will be delayed.
  • Interruptable job - provide a mechanism for having the Job execution interrupted by implementing a callback method interrupt(), which will be called when scheduler's interrupt method is invoked on the Job.
That's all, in short, about how to integrate Quartz scheduler library in a web application.

6 comments:

Naveen D R said...

Hi,
When Quartz is initialized thru initializer servlet, the factory object needs to be put in servlet context. Can i create scheduler object and put it in servlet context and down the line in app, i can simply get scheduler from the context and use it.

vishalvishnoi said...

Can you give some code, how can we schedule single job with multiple trigger.

Anonymous said...

I am having trouble shutting down my scheduler whe using QuartzInitializerServlet.

I keep getting SchedulerException : Scheduler with name 'xyz' already exists.

Any ideas? Is this because of the web.xml entries for the QuartzInitializer.

vikram said...

Hi ,

Actually i have requirement to create scheduler for Report generation of Log files .Using Web app(jsp/servlet) .A user should be able to create schedule to generate report .So how can i perform this using Quartz .How can i customize this with User input and generate report .

thanks

Anonymous said...

Welcome to the 2moons dil, In here you can buy the 2moons gold, Do you know that the 2moon dil in the game is very important, If you had more cheap 2moons gold. I think you can get the tall level, quickly come here to buy 2moons dil.

Durante said...

I was looking for such a reference material for Quartz. Thank you very much for posting..

Book Review: Spring Start Here: Learn what you need and learn it well

  Spring Start Here: Learn what you need and learn it well by Laurentiu Spilca My rating: 5 of 5 stars This is an excellent book on gett...