StatefulJob
:package my.com.petah.common.util.cron;Note that I am using the Jakarta commons logging. I also need to convert the time when the job ran previously into a
import org.apache.commons.logging.*;
import org.quartz.*;
import java.text.*;
import java.util.*;
import java.util.Calendar;
public class ConsolidateAttendance
implements Job, StatefulJob {
private static Log log
= LogFactory.getLog(ConsolidateAttendance.class);
private static DateFormat df
= new SimpleDateFormat("yyyymmdd HH:mm");
private static Date parse(String val) {
if (val == null) return null;
try { return df.parse(val); }
catch (ParseException e) { return null; }
}
public void execute(JobExecutionContext ctx)
throws JobExecutionException {
JobDetail job = ctx.getJobDetail();
JobDataMap props = job.getJobDataMap();
Date last = parse(props.getString("last-run"));
Calendar cal = Calendar.getInstance();
cal.clear(Calendar.SECOND);
cal.clear(Calendar.MILLISECOND);
Date curr = cal.getTime();
if (log.isInfoEnabled()) {
log.info(
job.getFullName()
+ " executes. Last run:"
+ last + " current:" + curr);
}
doExecute(ctx, last, curr);
props.put("last-run", df.format(curr));
}
public void doExecute(
JobExecutionContext ctx, Date last, Date curr) {
// job logic here
}
}
String
because, through configuration parameter org.quartz.jobStore.useProperties
, I have chosen to store job property values as String
s to avoid class versioning problems. The main logic of the job should be placed in the doExecuteMethod
.In creating the job and scheduling it, it seems reasonable to do it outside of the webapp in Tomcat. This can be done through RMI. The additional parameters needed in
quartz.properties
for this purpose is given below:
org.quartz.scheduler.rmi.export: true
org.quartz.scheduler.rmi.registryHost: localhost
org.quartz.scheduler.rmi.registryPort: 1099
org.quartz.scheduler.rmi.createRegistry: true
Now to get the RMI client program to work properly requires a bit of experimentation because Quartz documentation is a bit inadequate. For example, in all of the examples,
Scheduler
class has been used. But with RMI, we actually need to use QuartzScheduler_Stub
class instead. This class is method-for-method equivalent to QuartzScheduler
class. Methods signatures between Scheduler
and QuartzScheduler
are different because the latter typically would require a mysterious SchedulingContext
as its first parameter. We also need to bind with QuartzScheduler_Stub
with name "QuartzScheduler_$_NON_CLUSTERED
" in the RMI registry. See the start of the main
function below:
package my.com.petah.common.util.cron;
import org.apache.commons.logging.*;
import org.quartz.core.*;
import org.quartz.*;
import java.rmi.registry.*;
public class CronInit {
static Log log
= LogFactory.getLog(CronInit.class);
public static void main(String args[])
throws Exception {
Registry reg = LocateRegistry.getRegistry();
if (log.isInfoEnabled()) {
String n[] = reg.list();
for (int i = 0; i < n.length; ++i)
log.info("rmi registry object:" + n[i]);
}
SchedulingContext ctx = new SchedulingContext();
ctx.setInstanceId("petah.scheduler");
QuartzScheduler_Stub sched
= (QuartzScheduler_Stub) reg.lookup(
"QuartzScheduler_$_NON_CLUSTERED");
if (log.isInfoEnabled()) {
log.info(
"scheduler retrieved:"
+ sched.getSchedulerName());
String g[] = sched.getJobGroupNames(ctx);
for (int i = 0; i < g.length; ++i) {
String n[] = sched.getJobNames(ctx, g[i]);
for (int j = 0; j < n.length; ++j)
log.info("defined job: " + g[i] + "." + n[j]);
}
}
Note that you can give any string as the instance id for the mysterious
SchedulingContext
object. Once the scheduler has been successfully retrieved, the code prints the jobs already defined in the scheduler.Next, I need to create the job. I want the job to still exist even when it is not scheduled, i.e., it does not have a trigger attached to it. The job is said to be durable. Also, I want the job to persist between Quartz runs. It is said to be non-volatile in this case. However, before I create a job, I check whether the job is already there.
JobDetail job = sched.getJobDetail(
ctx, "consolidate-attendance",
"daily-attendance-group");
if (job != null) {
if (log.isInfoEnabled())
log.info(job.getFullName()
+ " already defined.");
} else {
job = new JobDetail("consolidate-attendance",
"daily-attendance-group", ConsolidateAttendance.class,
/* volatile */ false, /* durable */ true,
/* recover */ false);
sched.addJob(ctx, job, false);
}
The
JobDetail
object created above ties the ConsolidateAttendance
class defined earlier with a job with name consolidate-attendance
in the group daily-attendance-group. Actually, you do not have to explicitly add the job the scheduler. You could have schedule it with the trigger to be defined shortly. But I want to test whether I could add a "dangling" job and then schedule it later.Next, the trigger. Again, I check whether trigger by the given name is there already before creating it.
Trigger trig = sched.getTrigger(
ctx, "every-some-minutes",
"debug-trigger-group");
if (trig != null) {
if (log.isInfoEnabled())
log.info(trig.getFullName()
+ " already defined.");
} else {
trig = new CronTrigger(
"every-some-minutes",
"debug-trigger-group", "0 0/5 * * * ?");
}
Note that a trigger has a name and a group too. In the above, the name is "
every-some-minutes
" and the group is "debug-trigger-group
". The cron expression "0 0/5 * * * ?
" says that the trigger will fire every 5 minutes starting from the zeroth minute of the hour. I was hoping I could add a trigger to the scheduler similar to what is possible with a job but there is no way to do this.Now the job could be scheduled by attaching it to the trigger. I first check whether the trigger has already got a job tied to it already.
String jobGroup = trig.getJobGroup();
String jobName = trig.getJobName();
if (jobGroup != null && jobName != null) {
if (log.isInfoEnabled()) {
log.info("trigger " + trig.getFullName()
+ " already attached to job "
+ jobGroup + "." + jobName);
}
} else {
trig.setJobGroup("daily-attendance-group");
trig.setJobName("consolidate-attendance");
sched.scheduleJob(ctx, trig);
if (log.isInfoEnabled()) {
log.info("job " + job.getFullName()
+ " scheduled with trigger "
+ trig.getFullName());
}
}
}
}
Note that there are two versions of the method
scheduleJob
. You cannot use the version that accepts both a job and a trigger because internally the scheduler would try to add the job to itself. Since I have already done this, the method would fail. You need to tie the job to the trigger through methods in the Trigger
class and use the scheduleJob
that only accepts a trigger.That is it. Running the
CronInit
class above on the same machine where the webapp is running would create the job and the schedule it with the trigger. Below is a sample of the log from the scheduler:
...
Connected to server
INFO: Server startup in 5315 ms
INFO: daily-attendance-group.consolidate-attendance executes.
Last run:Wed Jan 18 22:00:00 MYT 2006
current:Sun Jun 18 22:05:00 MYT 2006
INFO: daily-attendance-group.consolidate-attendance executes.
Last run:Wed Jan 18 22:05:00 MYT 2006
current:Sun Jun 18 22:10:00 MYT 2006
INFO: daily-attendance-group.consolidate-attendance executes.
Last run:Wed Jan 18 22:10:00 MYT 2006
current:Sun Jun 18 22:15:00 MYT 2006
INFO: daily-attendance-group.consolidate-attendance executes.
Last run:Wed Jan 18 22:15:00 MYT 2006
current:Sun Jun 18 22:20:00 MYT 2006
INFO: daily-attendance-group.consolidate-attendance executes.
Last run:Wed Jan 18 22:20:00 MYT 2006
current:Sun Jun 18 22:25:00 MYT 2006
...
7 comments:
Can you please let me know what is the maximum limit of DATE which we can give, while using quartz.jar.
Ex->22-Jan-7000 so which is the maximum year which we can give to schedule.
Can you please mail me the details on mayur.bhatnagar@gmail.com
What if you are trying to connect to a clustered scheduler?
Oh, I have not done any serious Java development for sometime already. I figure if you want to use clustered scheduler, you will not start it from within Tomcat? To connect to it through RMI you probably need to bind to the clustered instance instead. There could be more things involved. Hopefully you have found your solution already.
hi this is karan,
thx for ur effort..
Can u help me in storing the log of fired,misfired or completed trigger..
i mean how can i store all this info in database table for future refrence that which trigger fired when..
mail me at karan.gondara@gmail.com
or jst post a reply over here.
thx a lot
Hi Karan,
In the example I have above, I store the time when a job gets triggered in a job property. Alternatively, you should be able to store the time in a column of a database table of your choice.
QuartzScheduler_Stub?
I think that you have to replace QuartzScheduler_Stub for RemotableQuartzScheduler interfase.
Thanks for the suggestion. Maybe I will try it when I've mustered enough energy to start looking at Java web development again.
The code I posted is like 2 years and a month old...
Post a Comment