In a recent project I was working on I had a requirement to be able to set up Quartz jobs programatically as the application is running. So in other words, when the app is started, there are no jobs scheduled or running, but through a user interface, create jobs and schedule them. I could not find any info on how to do this effectively in Grails, but found it to be easy. Real Easy!!!
Start by installing Quartz;
grails install-plugin quartz
Typically when creating Quartz Jobs in Grails, you would now go ahead and do “grails create-job”, but doing so will automatically trigger the job to run every minute (even if you leave the trigger off) as a default.
Instead, we are going to create a Service to run as a Job. That’s right you can use regular grails services as a Job Class with some minor modifications. And all the Grails Service goodness comes along with it
grails create-service JobScheduler
To make the service to act as a Job, you need to do a few things. The service needs to implement the Job Interface and has to include an execute method (I also needed to use the execute method that took the JobExecutionContext) so your JobScheduler class looks like this;
import org.quartz.Job
import org.quartz.JobExecutionContext;
class JobScheduler implements Job{
boolean transactional = false
public void execute (JobExecutionContext jobExecutionContext) {
println “I have been triggered to run ” + new Date()
}
}
In this example, my job is just printing a statement so I know that the job has run. So now we need to schedule the actual job. Triggering a job can be done through a controller, service or wherever you like. And make sure you check out the JavaDocs for SimpleTrigger and TriggerUtils, they are your friends!!! The only thing to remember is to include the quartzScheduler Bean from the app context
Here are some examples of how you can do it.
Firstly, schedule a one-off job so you just schedule your JobService to be called at some time in the future, this is quite easily done with the SimpleTrigger. Add the following to your controller
def quartzScheduler
And then add the following action
def scheduleOneOff() {
def jobDetail = new JobDetail(“myJob”, null, JobScheduler.class);
def trigger = new SimpleTrigger(“myTrigger”, null, new Date() + 1)
quartzScheduler.scheduleJob(jobDetail, trigger);
}
How easy was that?!?! Its pretty self explanatory, but as you can see we create a JobDetail, which passes the job name, “myJob”, group name (null in this instance) and you pass the Job Class. This is where you tell it about your service you previously created.
We then create a trigger for our Job which in this instance is a simple trigger with the name “myTrigger”, and again a null group and start date in 1 day (you can obviously set it to whatever you want by passing a valid date object).
Finally using the quartzScheduler bean, you schedule the job by passing the jobDetail and trigger to the scheduleJob method.
What about scheduling a recurring job that starts at 2am on the 1st of every month? Easy, use the makeMonthlyTrigger method from the TriggerUtils anonymous class;
def scheduleMonthly() {
def jobDetail = new JobDetail(“myJob”, null, JobScheduler.class);
def trigger = TriggerUtils.makeMonthlyTrigger(1, 2, 0);
trigger.setStartTime(new Date() + 1)
trigger.setName(“myTrigger”);
quartzScheduler.scheduleJob(jobDetail, trigger);
}
Things are very similar except that we have used the makeMonthlyTrigger and passed the start date, hour and minute. We have also told it to start in one day from today (as before) and have given our trigger a name.
There are helper methods to set triggers to happen on the minute, hour, week, month etc and of course you can schedule using your handy Cron expressions too.
By passing JobDetail parameters to your JobService, you could theoretically just use that JobService as a scheduler wrapper service and it can then be a generic scheduler for any job you may want to trigger.
RSS feed for comments on this post. TrackBack URL
Hi,
I have implemented this example as I need to be able to create jobs at runtime but I am having problems with the service which implements Job.
The problem is that I cannot inject any of my services or grailsApplication in to it, I get the error cannot invoke ‘myMethod’ on a null object or cannot get property ‘metadata’ on a null object.
My code is:
import org.quartz.Job
import org.quartz.JobExecutionContext
class ReplenishmentService implements Job {
boolean transactional = false
def grailsApplication
void execute(JobExecutionContext jobExecutionContext) {
println grailsApplication.metadata['app.name']
}
}
Thanks for the insight. Your basic example worked very well for me. I extended my service with another method that accesses the database via injected dataSource and groovy.sql.Sql. I found the dataSource to be null when this method was called from the execute method. When the service method was called directly from an integration test or from the controller (before or after the scheduleJob call) the dataSource was initialized and worked as expected.
I have no idea how to find the cause but have a simple demonstration example I could pass along.
Danny/Dennis, the issue is that when using this method of running a quartz job, the job itself is disconnected from your session and therefore none of the datasource or services become available to you.
To get it to work, you need to inject the hibernate session through the spring config. And I found this to be a bit painful. I can’t find the references, but do a Google search. But this page helped me quite a lot http://stackoverflow.com/questions/1860572/grails-quartz-job-has-no-hibernate-session-after-upgrade-causing-lazyinitializat (same kind of issue)
For my purposes, I went back to creating the jobs and could use the MyJob.schedule() methods effectively to schedule my jobs programmatically. The other issue I had to overcome was remove any triggers at startup and I discovered that it was as simple as;
static triggers = {}
ie/ pass an empty triggers object!!