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.

2 thoughts on “How to setup a clustered Quartz scheduler in Grails 2.0.0”

  1. This is good information. My quartz.properties file was not being found with the string “classpath:conf/Quartz.properties”. However, once I removed the conf directory and used “classpath:Quartz.properties” the file was picked up.

    The thread here talks about how grails copies files from certain folders and puts them on the classpath, not entire folders. http://grails.1312388.n4.nabble.com/Where-to-put-classpath-resources-td3050347.html

  2. i m getting this error. could u tell me why it is.

    NAME: MyClusteredScheduler
    | Error 2014-06-30 05:55:54,233 [localhost-startStop-1] ERROR jdbcjobstore.JobStoreTX – ClusterManager: Error managing cluster: Failure identifying failed instan
    heduler_state’ doesn’t exist
    Message: Failure identifying failed instances when checking-in: Table ‘schedulerproject.qrtz_scheduler_state’ doesn’t exist
    Line | Method
    ->> 3343 | findFailedInstances in org.quartz.impl.jdbcjobstore.JobStoreSupport
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    | 3393 | clusterCheckIn in ”
    | 3263 | doCheckin . . . . . . . . . . . in ”
    | 3869 | manage in org.quartz.impl.jdbcjobstore.JobStoreSupport$ClusterManager
    | 3854 | initialize . . . . . . . . . . . in ”
    | 678 | schedulerStarted in org.quartz.impl.jdbcjobstore.JobStoreSupport
    | 526 | start . . . . . . . . . . . . . in org.quartz.core.QuartzScheduler
    | 143 | start in org.quartz.impl.StdScheduler
    | 32 | startup . . . . . . . . . . . . in JobScheduler
    | 5 | doCall in BootStrap$_closure1
    | 308 | evaluateEnvironmentSpecificBlock in grails.util.Environment
    | 301 | executeForEnvironment in ”
    | 277 | executeForCurrentEnvironment . . in ”
    | 334 | innerRun in java.util.concurrent.FutureTask$Sync
    | 166 | run . . . . . . . . . . . . . . in java.util.concurrent.FutureTask
    | 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
    | 615 | run . . . . . . . . . . . . . . in java.util.concurrent.ThreadPoolExecutor$Worker
    ^ 722 | run in java.lang.Thread

    Caused by MySQLSyntaxErrorException: Table ‘schedulerproject.qrtz_scheduler_state’ doesn’t exist
    ->> 411 | handleNewInstance in com.mysql.jdbc.Util
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    | 386 | getInstance in ”
    | 1052 | createSQLException . . . . . . . in com.mysql.jdbc.SQLError
    | 4096 | checkErrorPacket in com.mysql.jdbc.MysqlIO
    | 4028 | checkErrorPacket . . . . . . . . in ”
    | 2490 | sendCommand in ”
    | 2651 | sqlQueryDirect . . . . . . . . . in ”
    | 2683 | execSQL in com.mysql.jdbc.ConnectionImpl
    | 2144 | executeInternal . . . . . . . . in com.mysql.jdbc.PreparedStatement
    | 2310 | executeQuery in ”
    | 76 | executeQuery . . . . . . . . . . in com.mchange.v2.c3p0.impl.NewProxyPreparedStatement
    | 2948 | selectSchedulerStateRecords in org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    | 3307 | findFailedInstances . . . . . . in org.quartz.impl.jdbcjobstore.JobStoreSupport
    | 3393 | clusterCheckIn in ”
    | 3263 | doCheckin . . . . . . . . . . . in ”
    | 3869 | manage in org.quartz.impl.jdbcjobstore.JobStoreSupport$ClusterManager
    | 3854 | initialize . . . . . . . . . . . in ”
    | 678 | schedulerStarted in org.quartz.impl.jdbcjobstore.JobStoreSupport
    | 526 | start . . . . . . . . . . . . . in org.quartz.core.QuartzScheduler
    | 143 | start in org.quartz.impl.StdScheduler
    | 32 | startup . . . . . . . . . . . . in JobScheduler
    | 5 | doCall in BootStrap$_closure1
    | 308 | evaluateEnvironmentSpecificBlock in grails.util.Environment
    | 301 | executeForEnvironment in ”
    | 277 | executeForCurrentEnvironment . . in ”
    | 334 | innerRun in java.util.concurrent.FutureTask$Sync
    | 166 | run . . . . . . . . . . . . . . in java.util.concurrent.FutureTask
    | 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
    | 615 | run . . . . . . . . . . . . . . in java.util.concurrent.ThreadPoolExecutor$Worker
    ^ 722 | run in java.lang.Thread
    | Error 2014-06-30 05:55:54,357 [QuartzScheduler_MyClusteredScheduler-computer-11404122152056_MisfireHandler] ERROR jdbcjobstore.JobStoreTX – MisfireHandler: Err
    misfires.
    Message: Database error recovering from misfires.
    Line | Method
    ->> 3197 | doRecoverMisfires in org.quartz.impl.jdbcjobstore.JobStoreSupport
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    | 3946 | manage in org.quartz.impl.jdbcjobstore.JobStoreSupport$MisfireHandler
    ^ 3967 | run . . . . . . . in ”

    Caused by MySQLSyntaxErrorException: Table ‘schedulerproject.qrtz_triggers’ doesn’t exist
    ->> 411 | handleNewInstance in com.mysql.jdbc.Util
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    | 386 | getInstance in ”
    | 1052 | createSQLException in com.mysql.jdbc.SQLError
    | 4096 | checkErrorPacket in com.mysql.jdbc.MysqlIO
    | 4028 | checkErrorPacket in ”
    | 2490 | sendCommand in ”
    | 2651 | sqlQueryDirect . in ”
    | 2683 | execSQL in com.mysql.jdbc.ConnectionImpl
    | 2144 | executeInternal . in com.mysql.jdbc.PreparedStatement
    | 2310 | executeQuery in ”
    | 76 | executeQuery . . in com.mchange.v2.c3p0.impl.NewProxyPreparedStatement
    | 416 | countMisfiredTriggersInState in org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    | 3176 | doRecoverMisfires in org.quartz.impl.jdbcjobstore.JobStoreSupport
    | 3946 | manage in org.quartz.impl.jdbcjobstore.JobStoreSupport$MisfireHandler
    ^ 3967 | run . . . . . . . in ”
    Starting Jobs
    | Error 2014-06-30 05:55:54,369 [MyClusteredScheduler_QuartzSchedulerThread] ERROR core.ErrorLogger – An error occurred while scanning for the next triggers to f
    Message: Couldn’t acquire next trigger: Table ‘schedulerproject.qrtz_triggers’ doesn’t exist
    Line | Method
    ->> 2840 | acquireNextTrigger in org.quartz.impl.jdbcjobstore.JobStoreSupport
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    | 2755 | execute in org.quartz.impl.jdbcjobstore.JobStoreSupport$41
    | 3810 | executeInNonManagedTXLock in org.quartz.impl.jdbcjobstore.JobStoreSupport
    | 2751 | acquireNextTriggers in ”
    ^ 264 | run . . . . . . . . . . . in org.quartz.core.QuartzSchedulerThread

    Caused by MySQLSyntaxErrorException: Table ‘schedulerproject.qrtz_triggers’ doesn’t exist
    ->> 411 | handleNewInstance in com.mysql.jdbc.Util
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    | 386 | getInstance in ”
    | 1052 | createSQLException . . . in com.mysql.jdbc.SQLError
    | 4096 | checkErrorPacket in com.mysql.jdbc.MysqlIO
    | 4028 | checkErrorPacket . . . . in ”
    | 2490 | sendCommand in ”
    | 2651 | sqlQueryDirect . . . . . in ”
    | 2683 | execSQL in com.mysql.jdbc.ConnectionImpl
    | 2144 | executeInternal . . . . . in com.mysql.jdbc.PreparedStatement
    | 2301 | executeQuery in ”
    | 76 | executeQuery . . . . . . in com.mchange.v2.c3p0.impl.NewProxyPreparedStatement
    | 2570 | selectTriggerToAcquire in org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    | 2781 | acquireNextTrigger . . . in org.quartz.impl.jdbcjobstore.JobStoreSupport
    | 2755 | execute in org.quartz.impl.jdbcjobstore.JobStoreSupport$41
    | 3810 | executeInNonManagedTXLock in org.quartz.impl.jdbcjobstore.JobStoreSupport
    | 2751 | acquireNextTriggers in ”
    ^ 264 | run . . . . . . . . . . . in org.quartz.core.QuartzSchedulerThread
    org.quartz.JobPersistenceException: Couldn’t determine job existence (group1.job1): Unknown column ‘SCHED_NAME’ in ‘where clause’ [See nested exception: com.mysql
    nown column ‘SCHED_NAME’ in ‘where clause’]
    | Server running. Browse to http://localhost:8090/SchedulerProject
    | Error 2014-06-30 05:57:22,256 [QuartzScheduler_MyClusteredScheduler-computer-11404122152056_ClusterManager] ERROR jdbcjobstore.JobStoreTX – ClusterManager: Err
    nces when checking-in: Table ‘schedulerproject.qrtz_scheduler_state’ doesn’t exist
    Message: Failure identifying failed instances when checking-in: Table ‘schedulerproject.qrtz_scheduler_state’ doesn’t exist
    Line | Method
    ->> 3343 | findFailedInstances in org.quartz.impl.jdbcjobstore.JobStoreSupport
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    | 3393 | clusterCheckIn in ”
    | 3263 | doCheckin . . . . . in ”
    | 3869 | manage in org.quartz.impl.jdbcjobstore.JobStoreSupport$ClusterManager
    ^ 3906 | run . . . . . . . . in ”

    Caused by MySQLSyntaxErrorException: Table ‘schedulerproject.qrtz_scheduler_state’ doesn’t exist
    ->> 411 | handleNewInstance in com.mysql.jdbc.Util
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    | 386 | getInstance in ”
    | 1052 | createSQLException in com.mysql.jdbc.SQLError
    | 4096 | checkErrorPacket in com.mysql.jdbc.MysqlIO
    | 4028 | checkErrorPacket . in ”
    | 2490 | sendCommand in ”
    | 2651 | sqlQueryDirect . . in ”
    | 2683 | execSQL in com.mysql.jdbc.ConnectionImpl
    | 2144 | executeInternal . . in com.mysql.jdbc.PreparedStatement
    | 2301 | executeQuery in ”
    | 76 | executeQuery . . . in com.mchange.v2.c3p0.impl.NewProxyPreparedStatement
    | 2948 | selectSchedulerStateRecords in org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    | 3307 | findFailedInstances in org.quartz.impl.jdbcjobstore.JobStoreSupport
    | 3393 | clusterCheckIn in ”
    | 3263 | doCheckin . . . . . in ”
    | 3869 | manage in org.quartz.impl.jdbcjobstore.JobStoreSupport$ClusterManager
    ^ 3906 | run . . . . . . . . in ”
    | Error 2014-06-30 05:58:50,285 [QuartzScheduler_MyClusteredScheduler-computer-11404122152056_ClusterManager] ERROR jdbcjobstore.JobStoreTX – ClusterManager: Err
    nces when checking-in: Table ‘schedulerproject.qrtz_scheduler_state’ doesn’t exist
    Message: Failure identifying failed instances when checking-in: Table ‘schedulerproject.qrtz_scheduler_state’ doesn’t exist
    Line | Method
    ->> 3343 | findFailedInstances in org.quartz.impl.jdbcjobstore.JobStoreSupport
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    | 3393 | clusterCheckIn in ”
    | 3263 | doCheckin . . . . . in ”
    | 3869 | manage in org.quartz.impl.jdbcjobstore.JobStoreSupport$ClusterManager
    ^ 3906 | run . . . . . . . . in ”

    Caused by MySQLSyntaxErrorException: Table ‘schedulerproject.qrtz_scheduler_state’ doesn’t exist
    ->> 411 | handleNewInstance in com.mysql.jdbc.Util
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    | 386 | getInstance in ”
    | 1052 | createSQLException in com.mysql.jdbc.SQLError
    | 4096 | checkErrorPacket in com.mysql.jdbc.MysqlIO
    | 4028 | checkErrorPacket . in ”
    | 2490 | sendCommand in ”
    | 2651 | sqlQueryDirect . . in ”
    | 2683 | execSQL in com.mysql.jdbc.ConnectionImpl
    | 2144 | executeInternal . . in com.mysql.jdbc.PreparedStatement
    | 2301 | executeQuery in ”
    | 76 | executeQuery . . . in com.mchange.v2.c3p0.impl.NewProxyPreparedStatement
    | 2948 | selectSchedulerStateRecords in org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    | 3307 | findFailedInstances in org.quartz.impl.jdbcjobstore.JobStoreSupport
    | 3393 | clusterCheckIn in ”
    | 3263 | doCheckin . . . . . in ”
    | 3869 | manage in org.quartz.impl.jdbcjobstore.JobStoreSupport$ClusterManager
    ^ 3906 | run . . . . . . . . in ”
    Terminate batch job (Y/N)? y

Leave a Reply