1. Introduction
The Quartz Plugin allows your Grails application to schedule jobs to be executed using a specified interval or cron expression. The underlying system uses the Quartz Enterprise Job Scheduler configured via Spring, but is made simpler by the coding-by-convention paradigm.
Since version 1.0-RC3, this plugin requires Quartz version 2.1.x or higher and no longer supports Quartz version 1.8.x.
2. Scheduling Basics
2.1. Scheduling Jobs
To create a new job, run the grails create-job command and enter the name of the job. Grails will create a new job and place it in the grails-app/jobs directory:
class MyJob {
static group = 'MyGroup'
static description = 'Example job with Simple Trigger'
static triggers = {
simple(
name: 'mySimpleTrigger',
startDelay: 60000,
repeatInterval: 1000
)
}
void execute() {
println 'Job run!'
}
}
The above example will wait for 1 minute and after that will call the execute() method every second. The repeatInterval and startDelay properties are specified in milliseconds and must have Integer or Long type. If these properties are not specified, default values are applied (1 minute for repeatInterval property and 30 seconds for startDelay property). Jobs can optionally be placed in different groups.
The trigger name property must be unique across all triggers in the application.
By default, jobs will not be executed when running under the test environment.
2.1.1. Scheduling a Cron Job
Jobs can be scheduled using a cron expression. For those unfamiliar with cron, this means being able to create a firing schedule such as "At 8:00am every Monday through Friday" or "At 1:30am every last Friday of the month". (See the API docs for the CronTrigger class in Quartz for more info on cron expressions).
class MyJob {
static group = 'MyGroup'
static description = 'Example job with Cron Trigger'
static triggers = {
cron(
name: 'myTrigger',
cronExpression: '0 0 6 * * ?'
)
}
void execute() {
println 'Job run!'
}
}
The fields in the cronExpression are: (summarizing the Quartz CronTrigger Tutorial)
cronExpression: "s m h D M W Y"
| | | | | | - Year <<optional>>
| | | | | - Day of Week, 1-7 or SUN-SAT, ?
| | | | - Month, 1-12 or JAN-DEC
| | | - Day of Month, 1-31, ?
| | - Hour, 0-23
| - Minute, 0-59
- Second, 0-59
|
3. Understanding Triggers
3.1. Scheduling configuration syntax
Currently, plugin supports three types of triggers:
-
simple — executes once per defined interval (ex. “every 10 seconds”);
-
cron — executes job with cron expression (ex. “at 8:00am every Monday through Friday”);
-
custom — your implementation of Trigger interface.
Multiple triggers per job are allowed.
class MyJob {
static triggers = {
simple(
name: 'simpleTrigger',
startDelay: 10000,
repeatInterval: 30000,
repeatCount: 10
)
cron(
name: 'cronTrigger',
startDelay: 10000,
cronExpression: '0/6 * 15 * * ?',
timeZone: TimeZone.getTimeZone('GMT-8') // timeZone is optional
)
custom(
name: 'customTrigger',
triggerClass: MyTriggerClass,
myParam: myValue,
myAnotherParam: myAnotherValue
)
}
void execute() {
println 'Job run!'
}
}
With this configuration a job will be executed 11 times with a 30-second interval with the first run in 10 seconds after scheduler startup (simple trigger). It will also be executed each 6 seconds during the 15th hour (15:00:00, 15:00:06, 15:00:12, … — this configured by cron trigger), and also each time your custom trigger will fire.
Three kinds of triggers are supported with the following parameters:
-
simple:-
name— the name that identifies the trigger -
startDelay— delay (in milliseconds) between scheduler startup and first job’s execution -
repeatInterval— timeout (in milliseconds) between consecutive job’s executions -
repeatCount— trigger will fire job execution (1 + repeatCount) times and stop after that (specify zero here to have a one-shot job or -1 to repeat job executions indefinitely)
-
-
cron:-
name— the name that identifies the trigger -
startDelay— delay (in milliseconds) between scheduler startup and first job’s execution -
cronExpression— cron expression
-
-
custom:-
triggerClass— your class which implements Trigger interface any params needed by your trigger.
-
It is also possible to adjust properties in a trigger Closure by the Grails configuration since the triggers block is given access to the grailsApplication object.
3.1.1. Dynamic Job Scheduling
Starting from the 0.4.1 version, you can schedule job executions dynamically.
These methods are available:
// creates cron trigger
MyJob.schedule(String cronExpression, Map params)
// creates simple trigger: repeats job repeatCount+1 times with delay of repeatInterval milliseconds
MyJob.schedule(Long repeatInterval, Integer repeatCount, Map params)
// schedules one job execution to the specific date
MyJob.schedule(Date scheduleDate, Map params)
//schedules job's execution with a custom trigger
MyJob.schedule(Trigger trigger)
// force immediate execution of the job
MyJob.triggerNow(Map params)
// Each method (except the one for custom trigger) takes optional 'params' argument.
// You can use it to pass some data to your job and then access it from the job.
class MyJob {
void execute(context) {
println(context.mergedJobDataMap.foo)
}
}
// now in your controller (or service, or something else):
MyJob.triggerNow([foo: 'It Works!'])
4. Plugin Configuration
4.1. Configuring the plugin
The plugin supports configuration settings defined in grails-app/conf/application.yml.
quartz:
autoStartup: true
Currently supported options:
-
autoStartupControls automatic startup of the Quartz scheduler during application bootstrap (default: true). -
jdbcStoreSet to true if you want Quartz to persist jobs in your DB (default: false). You’ll also need to provide aquartz.propertiesfile and make sure that required tables exist in your db (see the Clustering section below for the sample config and automatic tables creation using Hibernate).
4.1.1. Logging
A log is auto-injected into your task Job class without having to enable it. To set the logging level, add something like this to your grails-app/conf/Config.groovy log4j configuration.
debug 'grails.app.jobs'
4.1.2. Hibernate Sessions and Jobs
Jobs are configured by default to have Hibernate Session bounded to thread each time a job is executed. This is required if you are using Hibernate, which requires open session (such as lazy loading of collections) or working with domain objects with unique persistent constraint (it uses Hibernate Session behind the scene). If you want to override this behavior (rarely useful) you can use sessionRequired property:
static sessionRequired = false
4.1.3. Configuring concurrent execution
By default, jobs are executed in concurrent fashion, so new job execution can start even if the previous execution of the same job is still running. If you want to override this behavior, you can use concurrent property. In this case Quartz’s StatefulJob will be used (you can find more info about it here).
static concurrent = false
4.1.4. Configuring Job Enabled
By default, all jobs are considered enabled. In some cases it may be desired to temporarily disable a job. This can be done by overriding the jobEnabled property behavior.
static jobEnabled = false
4.1.5. Configuring description
Quartz allows for each job to have a short description. This may be configured by adding a description field to your Job. The description can be accessed at runtime using the JobManagerService and inspecting the JobDetail object.
static description = 'Example Job Description'
4.1.6. Clustering
Quartz plugin doesn’t support clustering out-of-the-box now. However, you could use a standard Quartz clustering configuration. You’ll also need to set jdbcStore configuration option to true.
There are also two parameters for configuring store/clustering on jobs, volatility and durability (both are true by default) and one for triggers, volatility (also true by default). A volatile job and trigger will not persist between Quartz runs, and a durable job will live even when there are no triggers referring to it.
Read Quartz documentation for more information on clustering and job stores as well as volatility and durability.
Now that the plugin supports Quartz 2.1.x, you can use current versions of open source Terracotta see https://github.com/rvanderwerf/terracotta-grails-demo for an example app.
4.1.7. Recovering
Since 0.4.2 recovering from a 'recovery' or 'fail-over' situation is supported with the requestsRecovery job-level flag (false by default).
If a job "requests recovery", and it is executing during a 'hard shutdown' of the scheduler, (i.e., the process it is running within crashes, or the machine is shut off), then it is re-executed when the scheduler is started again. In this case, the JobExecutionContext.isRecovering() method will return true.
5. Reference
5.1. Command Line
5.1.1. create-job
Purpose
Create a job class
Examples
grails create-job MyJob
5.2. Triggers
5.2.1. cron
Purpose
A cron trigger that executes based on the defined cron expression
Examples
class MyJob {
static triggers = {
cron(
name: 'cronTrigger',
startDelay: 10000,
cronExpression: '0/6 * 15 * * ?',
timeZone: TimeZone.getTimeZone('GMT-8') // timeZone is optional
)
}
void execute() {
println 'Job run!'
}
}
5.2.2. custom
Purpose
A custom trigger that executes based on the result of a custom Trigger implementation
Examples
class MyJob {
static triggers = {
custom(
name: 'customTrigger',
triggerClass: MyTriggerClass,
myParam: myValue,
myAnotherParam: myAnotherValue
)
}
void execute() {
println 'Job run!'
}
}
5.2.3. simple
Purpose
A simple trigger that executes on a set interval.
Examples
class MyJob {
static triggers = {
simple(
name: 'simpleTrigger',
startDelay: 10000,
repeatInterval: 30000,
repeatCount: 10
)
}
}