How to setup a clustered Quartz scheduler in Grails 2.0.0

In my steps below, I'm using Grails 2.0.0 and Quartz 2.1.1. I'm also connecting to a local DB2 database.

1. Run "grails clean" on your application.

2. Add the "quartz-all-2.1.1.jar" and "c3p0-0.9.1.1.jar" (in the lib folder of your Quartz download) to your lib directory.

3. Right click on your Grails project and chose "Grails Tools -> Refresh Dependencies" (Alt+G, R)

* Note: You will need run steps 1 - 3 in order to get Grails to link the dependencies.

4. Add your Quartz.properties file to your "conf" directory (or somewhere else on your classpath). Here's the Quartz.properties file I used (you'll need to change the username and password).

#============================================================================
# Configure Main Scheduler Properties
#============================================================================

org.quartz.scheduler.instanceName = MyClusteredScheduler
org.quartz.scheduler.instanceId = AUTO

#============================================================================
# Configure ThreadPool
#============================================================================

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 25
org.quartz.threadPool.threadPriority = 5

#============================================================================
# Configure JobStore
#============================================================================

org.quartz.jobStore.misfireThreshold = 60000

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 = myDS
org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000

#============================================================================
# Configure Datasources
#============================================================================

org.quartz.dataSource.myDS.driver = com.ibm.db2.jcc.DB2Driver
org.quartz.dataSource.myDS.URL = jdbc:db2://localhost:50001/BATCH
org.quartz.dataSource.myDS.user = <some user>
org.quartz.dataSource.myDS.password = <some password>
org.quartz.dataSource.myDS.maxConnections = 5
org.quartz.dataSource.myDS.validationQuery=select 0 from dual

5. In your Config.groovy file, add or modify you "grails.config.locations" property. Here's what I added:


grails.config.locations = [
        "classpath:conf/Quartz.properties"
]

6. I added the JobScheduler and HelloJob java classes to my src/java directory. These could be groovy or whatever, but I just stole the example from Quartz to get it working correctly.

JobScheduler.java


package sample.quartz.scheduler;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.CronScheduleBuilder.*;

import org.apache.log4j.Logger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

public class JobScheduler {
	private static Logger log = Logger.getLogger(JobScheduler.class);

	private static JobScheduler JOB_SCHEDULER = new JobScheduler();
	private Scheduler scheduler = null;

	public JobScheduler() {
	}

	public static JobScheduler getInstance() {
		return JOB_SCHEDULER;
	}

	public void startup() {
		try {
			// and start it off
			scheduler = StdSchedulerFactory.getDefaultScheduler();
			System.out.println("NAME: " + scheduler.getSchedulerName());
			scheduler.start();

			// define the job and tie it to our HelloJob class
			JobDetail job = newJob(HelloJob.class)
					.withIdentity("job1", "group1")
					.build();

			// Trigger a job that repeats every 20 seconds
			Trigger trigger = newTrigger()
					.withIdentity("trigger1", "group1")
					.withSchedule(cronSchedule("0/20 * * * * ?"))
					.build();

			System.out.println("Starting Jobs");
			// Tell quartz to schedule the job using our trigger
			scheduler.scheduleJob(job, trigger);
			scheduler.start();

		} catch (SchedulerException se) {
			se.printStackTrace();
		}
	}

	public void shutdown() {
		try {
			scheduler.shutdown();
		} catch (SchedulerException se) {
			se.printStackTrace();
		}
	}
}

HelloJob.java


package sample.quartz.scheduler;

import java.util.Date;

import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class HelloJob implements Job {
	private static Logger log = Logger.getLogger(HelloJob.class);

	public HelloJob() {
	}

	public void execute(JobExecutionContext context)
			throws JobExecutionException {
		System.out.println("Hello!  HelloJob is executing. " + new Date());
	}
}

7. In your BootStrap.groovy file, add...


import sample.quartz.scheduler.JobScheduler

class BootStrap {

        def init = { servletContext ->
                JobScheduler.getInstance().startup()
        }

        def destroy = {
                JobScheduler.getInstance().shutdown()
        }
}

That's it! Start your server. I tested it by running two servers. So,

grails -Dserver.port=8080 run-app

and then

grails -Dserver.port=8090 run-app

You will see that the first server to come up will run the HelloJob.java. I then tested the cluster by shutting off the first server. The second server picked up the scheduler (within 20 seconds, since that's what was specified in the property file) and started running with it.

One problem I ran into trying to set this up was the error below. I had forgotten to c3po-0.9.1.1.jar along with quartz-all-2.1.1jar to the lib directory. Once I did that (and refreshed the dependencies), this error went away.

Issue:

ERROR context.GrailsContextLoader  - Error executing bootstraps: java.lang.NoClassDefFoundError: com/mchange/v2/c3p0/ComboPooledDataSource

Hope that helps.