I am using the quartz plugin with Grails 1.3.7. I have a need to load balance/cluster a server app that uses quartz jobs. Apparently this is supported but I am finding that
To cluster the Quartz plugin in Grails, there are some files you need to include in your project. First, install the grails-app/conf/QuartzConfig.groovy
and make sure jdbcStore
is enabled.
quartz {
autoStartup = true
jdbcStore = true
waitForJobsToCompleteOnShutdown = true
}
Next, install the Hibernate configuration files relevant to the database to which you will be connecting. For example, with Oracle, the base Hibernate xml config at grails-app/conf/hibernate/hibernate.cfg.xml
is:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
'-//Hibernate/Hibernate Configuration DTD 3.0//EN'
'http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd'>
<hibernate-configuration>
<session-factory>
<mapping resource="Quartz.oracle.hbm.xml"/>
</session-factory>
</hibernate-configuration>
The actual Quartz-Hibernate SQL file for this example will be named Quartz.oracle.hbm.xml
and will reside in the same directory. These files should be available at the Quartz plugin on GitHub (https://github.com/nebolsin/grails-quartz), under src/templates/sql
. Note, that these scripts only seem to work for DataSource create
and create-drop
, so you'll need to manually create the Quartz tables on an update
, if they don't already exist from a previous run.
Create a grails-app/conf/quartz/quartz.properties
file, and edit is to fit your business needs:
/* Have the scheduler id automatically generated for
* all schedulers in a cluster */
org.quartz.scheduler.instanceId = AUTO
/* Don't let Quartz "Phone Home" to see if new versions
* are available */
org.quartz.scheduler.skipUpdateCheck = true
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
/* Configure Quartz for only one thread as the only job
* should run once per day */
org.quartz.threadPool.threadCount = 4
/* Give the thread a Thread.MIN_PRIORITY level*/
org.quartz.threadPool.threadPriority = 1
/* Allow a minute (60,000 ms) of non-firing to pass before
* a trigger is called a misfire */
org.quartz.jobStore.misfireThreshold = 60000
/* Handle only 2 misfired triggers at a time */
org.quartz.jobStore.maxMisfiresToHandleAtATime = 2
/* Check in with the cluster every 5000 ms*/
org.quartz.jobStore.clusterCheckinInterval = 5000
/* Use the Oracle Quartz Driver to communicate via JDBC */
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
/* Have Quartz handle its own transactions with the database */
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
/* Define the prefix for the Quartz tables in the database*/
org.quartz.jobStore.tablePrefix = QRTZ_
/* Tell Quartz it is clustered */
org.quartz.jobStore.isClustered = true
/* Tell Quartz that properties passed to the job call are
* NOT all String objects */
org.quartz.jobStore.useProperties = false
/* Detect the jvm shutdown and call shutdown on the scheduler */
org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownhook.cleanShutdown = true
/* Log the history of triggers and jobs */
org.quartz.plugin.triggerHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin
org.quartz.plugin.jobHistory.class = org.quartz.plugins.history.LoggingJobHistoryPlugin
Note from the above properties, you can set org.quartz.plugins
in the Log4j setup of Config.groovy
to log relevant job and trigger triggering information. I think info
level should suffice.
Edit, or create, scripts/_Events.groovy
and add the following war modification closure. This fixes a known Quartz plugin bug to install the correct quartz.properties
, instead of a blank one from the plugin, in to the final war file.
eventCreateWarStart = { warName, stagingDir ->
// Make sure we have the correct quartz.properties in the
// correct place in the war to enable clustering
ant.delete(dir:"${stagingDir}/WEB-INF/classes/quartz")
ant.copy(file:"${basedir}/grails-app/conf/quartz/quartz.properties",
todir:"${stagingDir}/WEB-INF/classes")
}
And you should be done...
P.S. If you are using an Oracle database, add the following to BuildConfig.groovy
in the dependencies block, so that you have access to the Quartz-Oracle communication drivers:
runtime("org.quartz-scheduler:quartz-oracle:1.7.2") {
// Exclude quartz as 1.7.3 is included from the plugin
excludes('quartz')
}
P.P.S The sql files at the link above are just the SQL. To make it in to a hibernate file, just surround each individual SQL command with a Hibernate database-object
node, like so (again w/ Oracle example):
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
'-//Hibernate/Hibernate Mapping DTD 3.0//EN'
'http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd'>
<hibernate-mapping>
<database-object>
<create>
CREATE TABLE QRTZ_JOB_DETAILS (
JOB_NAME VARCHAR2(200) NOT NULL,
JOB_GROUP VARCHAR2(200) NOT NULL,
DESCRIPTION VARCHAR2(250) NULL,
JOB_CLASS_NAME VARCHAR2(250) NOT NULL,
IS_DURABLE VARCHAR2(1) NOT NULL,
IS_VOLATILE VARCHAR2(1) NOT NULL,
IS_STATEFUL VARCHAR2(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR2(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (JOB_NAME,JOB_GROUP)
)
</create>
<drop>DROP TABLE QRTZ_JOB_DETAILS</drop>
<dialect-scope name='org.hibernate.SomeOracleDialect' />
</database-object>
...
<database-object>
<create>INSERT INTO QRTZ_LOCKS VALUES('TRIGGER_ACCESS')</create>
<drop></drop>
<dialect-scope name='org.hibernate.SomeOracleDialect' />
</database-object>
...
</hibernate-mapping>
The dialect-scope
tells Hibernate with which Database dialects the create and drop nodes should be used. You can try leaving it out and see if it works, otherwise you may have to add the MySql dialect used by your Grails DataSource.