I'm reading Quartz documentation and trying to understand can I pass inside Job instance method instead of class.
For example, in case with class I need to write:
public class MyJobClass implements Job {
public MyJobClass() {
// Instances of Job must have a public no-argument constructor.
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
JobDataMap data = context.getMergedJobDataMap();
System.out.println("someProp = " + data.getString("someProp"));
}
}
And defining a Job Instance like:
JobDetail job1 = newJob(MyJobClass.class) // what about method here
.withIdentity("job1", "group1")
.usingJobData("someProp", "someValue")
.build();
By the same principle, I tried to define job instance passing method like:
// define the job
JobDetail job = newJob(testMethod())
.withIdentity("job1", "group1")
.build();
And method looks like:
private Class<? extends Job> testMethod() {
//...
return null;
}
But I get the error:
java.lang.IllegalArgumentException: Job class cannot be null.
Updated:
I return null in method, because if I don't do this I get:
Your testMethod() method returns null. Quartz does not accept null and fails.
Quartz wants to manage jobs by itself so it is why you are only allowed to pass class not instance.
Why do you need to provide your own instance? If you want to make it "persistent" to keep state between executions then Quartz provides stateful job concept, see http://www.quartz-scheduler.org/api/2.3.0/org/quartz/StatefulJob.html
Related
I'm using quartz-scheduler 1.8.5. I've created a Job implementing StatefulJob. I schedule the job using a SimpleTrigger and StdSchedulerFactory.
It seems that I have to update the Trigger's JobDataMap in addition to the JobDetail's JobDataMap in order to change the JobDataMap from inside the Job. I'm trying to understand why it's necessary to update both? I noticed that the JobDataMap is set to dirty. Maybe I have to explicitly save it or something?
I'm thinking I'll have to dig into the source code of Quartz to really understand what is going on here, but I figured I'd be lazy and ask first. Thanks for any insight into the inner workings of JobDataMap!
Here's my job:
public class HelloJob implements StatefulJob {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
int count = context.getMergedJobDataMap().getInt("count");
int count2 = context.getJobDetail().getJobDataMap().getInt("count");
//int count3 = context.getTrigger().getJobDataMap().getInt("count");
System.err.println("HelloJob is executing. Count: '"+count+"', "+count2+"'");
//The count only gets updated if I updated both the Trigger and
// JobDetail DataMaps. If I only update the JobDetail, it doesn't persist.
context.getTrigger().getJobDataMap().put("count", count++);
context.getJobDetail().getJobDataMap().put("count", count++);
//This has no effect inside the job, but it works outside the job
try {
context.getScheduler().addJob(context.getJobDetail(), true);
} catch (SchedulerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//These don't seem to persist between jobs
//context.put("count", count++);
//context.getMergedJobDataMap().put("count", count++);
}
}
Here's how I'm scheduling the job:
try {
// define the job and tie it to our HelloJob class
JobDetail job = new JobDetail(JOB_NAME, JOB_GROUP_NAME,
HelloJob.class);
job.getJobDataMap().put("count", 1);
// Trigger the job to run now, and every so often
Trigger trigger = new SimpleTrigger("myTrigger", "group1",
SimpleTrigger.REPEAT_INDEFINITELY, howOften);
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
return job;
} catch (SchedulerException e) {
throw e;
}
Update:
Seems that I have to put the value into the JobDetail's JobDataMap twice to get it to persist, this works:
public class HelloJob implements StatefulJob {
public HelloJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
int count = (Integer) context.getMergedJobDataMap().get("count");
System.err.println("HelloJob is executing. Count: '"+count+"', and is the job stateful? "+context.getJobDetail().isStateful());
context.getJobDetail().getJobDataMap().put("count", count++);
context.getJobDetail().getJobDataMap().put("count", count++);
}
}
This seems like a bug, maybe? Or maybe there's a step I'm missing to tell the JobDetail to flush the contents of its JobDataMap to the JobStore?
I think your problem is with using the postfix ++ operator - when you do:
context.getJobDetail().getJobDataMap().put("count", count++);
you're setting the value in the map to count and THEN incrementing count.
To me it looks like you wanted:
context.getJobDetail().getJobDataMap().put("count", ++count);
which would only need to be done once.
As you know, in Quartz, the trigger and the job are separate, rather than combined with some schedulers. They might be allowing you to add values to the datamap which are specific at the trigger level rather than the job level, etc.
I think it allows you to execute the same end job with a different set of data, but still have some common data at the job level.
As scpritch76 answered, the job and trigger are separate so that there can be many triggers (schedules) for a given job.
The job can have some base set of properties in the JobDataMap, and then the triggers can provide additional properties (or override base properties) for particular executions of the job in their JobDataMaps.
#PersistJobDataAfterExecution
#DisallowConcurrentExecution
public class DynamicTestJob implements Job
{
private static final Logger log = LoggerFactory.getLogger(DynamicTestJob.class);
#Override
public void execute(final JobExecutionContext context)
{
final JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
Integer counter = (Integer) jobDataMap.get("counter");
if (counter == null)
{
counter = 1;
}
else
{
counter++;
}
jobDataMap.put("counter", counter);
System.out.println(counter);
}
}
I´m trying to build a method which creates scheduled jobs. These job call a URL.
public synchronized void scheduleNewJob(int jobNr, long newRate) throws NoSuchMethodException {
ScheduledFuture job = jobsMap.get(jobNr);
if (job != null) {// job was already scheduled, we have to cancel
// it
job.cancel(true);
}
// reschedule the same method with a new rate
final String methodName = "callApi";
Method method = new ApiCallerHelper().getClass().getMethod(methodName, String.class);
job = taskScheduler
.scheduleAtFixedRate(new ScheduledMethodRunnable(targetClass, method), newRate);
To do this I need to pass a URI param to my method (callApi).
Is there a possibility to do this? Or maybe a better way?
According to Spring documentation on ScheduledMethodRunnable:
...meant to be used for processing of no-arg scheduled methods.
I guess you can create a class wrapper with parametrized constructor. Wrap a class with the scheduled method in it. And the refer to param from scheduled method, while the method itself still would be no-arg and void-returning.
I have the following method that builds a new JobDetail instance;
private JobDetail getJob(JobID jobID) throws SchedulerException {
Class<? extends Job> jobClass = jobID.getJobClass();
if(jobClass != null){
return JobBuilder
.newJob(jobClass)
.withIdentity(jobID.jobName(), jobID.jobGroup())
.requestRecovery(true)
.build();
}
return null;
}
Note: JobID is just a helper class of mine that provides all the necessary raw data required to build a JobDetail
Then another method that does the actual scheduling;
doSchedule(Scheduler scheduler, JobDetail job, String triggerName){
Trigger trigger = buildSomeTrigger(triggerName);
//check whether job exists in scheduler instance
if(scheduler.checkExists(job.getKey())){
//If trigger also exists, then it probably holds new info
//So, reschedule the existing job with the trigger
if(scheduler.checkExists(trigger.getKey())){
System.out.println("update job with Trigger");
job = job.getJobBuilder().storeDurably(true).build();
scheduler.addJob(job, true);
Trigger oldTrigger = scheduler.getTrigger(trigger.getKey());
scheduler.rescheduleJob(oldTrigger.getKey(), trigger);
}else{
//If trigger does not exist, it's an entirely new trigger
//Add the new trigger to the existing job
System.out.println("save new trigger for job");
trigger = trigger.getTriggerBuilder().forJob(job).build();
scheduler.scheduleJob(trigger);
}
}else{
//If job does not exist, schedule new job with the trigger
System.out.println("schedule new job with trigger");
scheduler.scheduleJob(job, trigger);
}
}
EDIT: Here's what getNewSchedulerInstance() looks like;
private Scheduler getNewSchedulerInstance() {
Scheduler scheduler = null;
try {
scheduler = new StdSchedulerFactory("quartz.properties").getScheduler();
scheduler.setJobFactory(cdiJobFactory); //<== Utilizes the JobFactory implementation specified in the CdiJobFactory.java class
scheduler.getListenerManager().addJobListener(new SimpleJobListener());
scheduler.getListenerManager().addTriggerListener(new SimpleTriggerListener());
} catch (SchedulerException ex) {
System.out.println("Scheduler instantiation failed!");
ex.printStackTrace();
}
return scheduler;
}
Then finally in another method, I have the following;
//This returns a new instance of Scheduler
Scheduler scheduler = getNewSchedulerInstance();
if(scheduler != null){
JobID jobID = new ExtendedJobID(); // <== extends JobID
JobDetail job = getJob(jobID);
doSchedule(scheduler, job);
}
Normally I expect this to print;
schedule new job with trigger
But instead it's printing;
save new trigger for job
EDIT: And hence throws the following exception;
org.quartz.JobPersistenceException: Couldn't store trigger 'GOALS.ACTIVATE_GOALS_0:0' for 'GOALS.ACTIVATE_GOALS' job:
The job (GOALS.ACTIVATE_GOALS) referenced by the trigger does not exist.
[See nested exception: org.quartz.JobPersistenceException: The job (GOALS.ACTIVATE_GOALS) referenced by the trigger does not exist.]
This suggests that the scheduler.checkExists(job.getKey()) returns true
scheduler.checkExists(trigger.getKey()) returns false
Yet exception says that The job (*jobName*) referenced by the (*trigger*) does not exist
So, this issuse only gets me confused as it seems that scheduler.checkExists(...) is failing to appropriately check the existence of the job or not in the scheduler
The Exception shouldn't even get thrown if the check did what I expect it to do;
But as you can see, the JobDetail is newly built and has not in any way been previously associated with the scheduler...
So I'm wondering whether i'm missing some Quartz Scheduler points?...
If so, can someone please explain what I might be doing wrong here?...
Or could this actually be a bug?...
Are there any work-arounds?
I try to get an async process running.
Based on this example: http://tomee.apache.org/examples-trunk/async-methods/README.html
But the method addWorkflow(Workflow workflow) will only return when the code in run(Workflow workflow) is fully completed.
Then when it returns and result.get(); is called I'll get the exception:
Caused by: java.lang.IllegalStateException: Object does not represent an acutal Future
Any suggestion what I'm missing?
#Singleton
public class WorkflowProcessor {
#EJB
private WorkflowManager workflowManager;
private final static Logger log = Logger.getLogger(WorkflowProcessor.class.getName());
public void runWorkflows(Collection<Workflow> workflows) throws Exception{
final long start = System.nanoTime();
final long numberOfWorkflows = workflows.size();
Collection<Future<Workflow>> asyncWorkflows = new ArrayList<>();
for(Workflow workflow : workflows){
Future<Workflow> w = addWorkflow(workflow);
asyncWorkflows.add(w);
}
log.log(Level.INFO, "workflow jobs added {0}", new Object[]{numberOfWorkflows});
for(Future<Workflow> result : asyncWorkflows){
result.get();
}
final long total = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start);
log.log(Level.INFO, "WorkflowProcessor->runWorkflows {0} workflows completed in:{1}", new Object[]{numberOfWorkflows, total});
}
#Asynchronous
#Lock(LockType.READ)
#AccessTimeout(-1)
private Future<Workflow> addWorkflow(Workflow workflow){
run(workflow);
return new AsyncResult<Workflow>(workflow);
}
private void run(Workflow workflow){
this.workflowManager.runWorkflow(workflow);
}
So the normal way would be to have the #Asynchronous method in another bean from the caller method.
#Stateless
public class ComputationProcessor {
#Asynchronous
public Future<Data> performComputation {
return new AsyncResult<Data>(null);
}
}
#Stateless
public class ComputationService {
#Inject
private ComputationProcessor mProcessor;
public void ...() {
Future<Data> result = mProcessor.performComputation();
...
}
}
As you discovered, it won't work if the #Asynchronous method is in the same bean than the caller.
The issue is that Java can't decorate the implicit this pointer.
In other words, the #Asynchronous annotation won't be processed and you're doing an ordinary method call.
You can inject your so singleton with a reference to itself (call this e.g. "self"), then call self.addWorkflow.
You might also want to consider running your async code in a stateless bean. You are using a read lock for addWorkflow, but runWorkflow still has a write lock. I think you have a dead lock now: you're holding the lock until work is done, but no work can be done until the write lock is released.
I'm trying to schedule a task depending on it's response. The task is something like:
public Date scheduledTask() {
Date nextRun;
// ...
nextRun = something();
// ...
return nextRun;
}
How can I make sure that the same task is called again when reaching the nextRun?
Thank you.
This is very simple with standard Quartz scheduler API. Inside your Job compute nextRun time and create a trigger with startAt() defined:
public class ScheduledJob implements Job {
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
final Date nextRun = something();
Trigger trigger = newTrigger().
startAt(nextRun).
forJob(context.getJobDetail()).
build();
context.getScheduler().scheduleJob(trigger);
}
}
Tested, works like a charm.
Follow the ideas mentioned here, then you should be able to have:
public class GuaranteeSchedule implements Trigger {
private Future<?> resultForNextRun;
private TaskScheduler scheduler;
public void scheduledTask() {
// 'this' is this trigger that is used by the scheduler
// and the method `nextExecutionTime` is automatically called by the scheduler
resultForNextRun = scheduler.schedule(theTask, this);
// theTask is the object that calls something()
}
// Implementing Trigger to add control dynamic execution time of this trigger
#Override
public Date nextExecutionTime(TriggerContext tc) {
// make sure the result for the previous call is ready using the waiting feature of Future
Object result = resultForNextRun.get();
// Use tc or other stuff to calculate new schedule
return new Date();
}
}
The rest, you should follow the configuration mentioned in the reference. I believe this would resolve the problem of depending the next call of a trigger on the result of the previous. You may also need to be careful about the first call of scheduledTask to make sure resultForNextRun != null.