Tuesday, June 13, 2006

OpenSymphony Quartz on Tomcat and Postgresql

It is always a bad thing to execute long running processes directly from a web page. It is worse when you need to tell your user to click a specific button at a specific time of day before she could see her consolidated reports.

To run processes asynchronously, OpenSymphony Quartz comes to the rescue. When I was a Java "technical lead" a while back, I had asked one of my junior developers to use it. But I did not really try it myself.

It is time now for my wife and I to use it for the school management system we are developing. We need to generate attendance records from students checking in and out of the school. It needs to run every morning some time after students have come and every afternoon after they have left the school. And we need Quartz for some other backend processes too.

As always, OpenSymphony documentation is all over the place and it took some time to gather the bits and pieces to configure Quartz to run on Tomcat and the Postgresql database. The first thing was to create the database tables as given in Quartz distribution (docs/dbTables/tables_postgres.sql). And per the FAQ, I created these indexes for performance sake:

create index idx_qrtz_t_next_fire_time
on qrtz_triggers(NEXT_FIRE_TIME);
create index idx_qrtz_t_state
on qrtz_triggers(TRIGGER_STATE);
create index idx_qrtz_t_nf_st
on qrtz_triggers(TRIGGER_STATE,NEXT_FIRE_TIME);
create index idx_qrtz_ft_trig_name
on qrtz_fired_triggers(TRIGGER_NAME);
create index idx_qrtz_ft_trig_group
on qrtz_fired_triggers(TRIGGER_GROUP);
create index idx_qrtz_ft_trig_n_g
on qrtz_fired_triggers(TRIGGER_NAME,TRIGGER_GROUP);
create index idx_qrtz_ft_trig_inst_name
on qrtz_fired_triggers(INSTANCE_NAME);
create index idx_qrtz_ft_job_name
on qrtz_fired_triggers(JOB_NAME);
create index idx_qrtz_ft_job_group
on qrtz_fired_triggers(JOB_GROUP);

Incidentally, tables_postgres.sql has a typo in it. If you simply copy the driverDelegateClass property setting from the comment into your quartz.properties you will be in for a surprise (Couldn't load delegate class).

Next, I copied quartz-all-1.5.2.jar (I am using version 1.5.2) and all jars from lib/core that I didn't already have into the webapp WEB-INF/lib.

After a few attempts, I ended up with these properties in my quartz.properties (in WEB-INF/classes):

org.quartz.dataSource.petahdb.jndiURL = java:comp/env/jdbc/petahdb

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate

org.quartz.jobStore.dataSource = petahdb
org.quartz.jobStore.useProperties = true

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 4

The datasource URL jdbc/petahdb refers to the datasource I have already defined in Tomcat context.xml and referenced in web.xml. Note that the word petahdb in "org.quartz.dataSource.petahdb.jndiURL" is an arbitrary name given to the datasource and it is used as the value for the org.quartz.jobStore.dataSource property.

To get Quartz running when Tomcat runs, as suggested in Quartz documentation, I simply put the fragment below in web.xml:

<servlet>
<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>
<load-on-startup>2</load-on-startup>
</servlet>

This was what I got when I finally managed to get Tomcat to run without errors:

INFO: Quartz Scheduler v.1.5.2 created.
INFO: Using thread monitor-based data access locking (synchronization).
INFO: Removed 0 Volatile Trigger(s).
INFO: Removed 0 Volatile Job(s).
INFO: JobStoreTX initialized.
INFO: Quartz scheduler 'QuartzScheduler' initialized from default
resource file in Quartz package: 'quartz.properties'
INFO: Quartz scheduler version: 1.5.2
INFO: Freed 0 triggers from 'acquired' / 'blocked' state.
INFO: Recovering 0 jobs that were in-progress at the time of the
last shut-down.
INFO: Recovery complete.
INFO: Removed 0 'complete' triggers.
INFO: Removed 0 stale fired job entries.
INFO: Scheduler QuartzScheduler_$_NON_CLUSTERED started.
INFO: QuartzInitializer: Scheduler has been started...
INFO: QuartzInitializer: Storing the Quartz Scheduler Factory in
the servlet context at key:
org.quartz.impl.StdSchedulerFactory.KEY

3 comments:

Unknown said...

I created a JIRA ticket for the typo, and they fixed it one day later.

http://jira.opensymphony.com/browse/QUARTZ-500

Anonymous said...

Appreciate the post. That has helped me.

Anonymous said...
This comment has been removed by a blog administrator.