How do I invoke a method on an existing object with Quartz? - java

I have a object, nicely configured with everything it needs to do its job. If I could just call run() on it once a day, my life would be complete.
To be clear, I know how to create a schedule and a trigger. But the methods to schedule all take JobDetail, which wants to create a new instance of my class. How do I use the one that I have?
In short, is there a nice way without Spring to call a method on my object using Quartz?

If you are using Quartz with Spring you can do the following :
Sample code
MethodInvokingJobDetailFactoryBean jobDetailfactory = new MethodInvokingJobDetailFactoryBean();
jobDetailfactory.setTargetObject(configuredObject);
jobDetailfactory.setTargetMethod("methodName");
Here configuredObject is your nicely configured object and methodName is the name of the method to be invoked. You can autowire the configuredObject into this class.

You can use Quartz JobBuilder to build Quartz JobDetail Object using your own jobDetails class, if I get you correctly. let me know if it is not required by you.
Suppose Job info is your own class having jobdetails. then you can use it below like this:
JobDataMap jobDataMap = new JobDataMap();
Map<String, Object> jobParams = jobInfo.getJobParams();
for (String paramKey : jobParams.keySet()) {
jobDataMap.put(paramKey, jobParams.get(paramKey));
}
jobBuilder.ofType((Class<? extends Job>) Class.forName(jobInfo.getJobClass()))
.withIdentity(jobInfo.getName(), jobInfo.getGroup())
.withDescription(jobInfo.getDescription()).storeDurably(jobInfo.isStoreDurably())
.usingJobData(jobDataMap);
JobDetail jobDetail = jobBuilder.build();

Here's some code (Kotlin)
fun createJobDetail(jobName: String, function: () -> Unit) = JobBuilder.newJob(MyJob::class.java)
.withIdentity(jobName)
.usingJobData(JobDataMap(mapOf(jobDataKey to function)))
.build()`
#DisallowConcurrentExecution
class MyJob : Job {
#Suppress("UNCHECKED_CAST")
override fun execute(context: JobExecutionContext) {
try {
(context.jobDetail.jobDataMap[jobDataKey] as () -> Unit)()
} catch(e: Exception) {
throw JobExecutionException(e)
}
}
}

Instead of using Quartz, you might be better off using the built-in java.util.concurrent. ScheduledExecutorService and its scheduleAtFixedRate() method.
For example:
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1,
new ThreadFactory() {
#Override
public Thread newThread(Runnable runnable) {
Thread t = Executors.defaultThreadFactory().newThread(runnable);
t.setDaemon(true);
return t;
}
});
scheduler.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
myLovelyObject.run();
}
}, 0, 24, TimeUnit.HOURS);
If you need to use Quartz, you could always store a reference to your object in a static field in the Job class. Not elegant, but not exactly the end of the world either.

Related

Spring boot: #Scheduler that updates cron espression without restarting

I Need to schedule a task on Spring Boot that reads a cron espression from the database. I did this using the #Scheduled annotation and reading a property inside a database, but my client is asking to be able to update the cron expression in the database and having it affect the scheduled without restarting the application. I know this isnt possible with the #Scheduled annotation, but would It be possible to schedule another task that extracts the cron expression every hour, and then feed the updated expression to the actual scheduled that executes the task? Basically updating the variable that Is Fed to the second scheduled. If this isnt possible, do you know any alternative ways to achieve this without using the #Scheduled annotation? Thank you.
You could try doing this using your own Runnable and a ScheduledExecutorService Which starts a thread to do what you are asking once every hour.
private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
public void chronJob Runner() {
final Runnable chronJobWorker = new Runnable() {
public void run() { //Request logic }
};
scheduler.scheduleAtFixedRate(beeper, 1, 60, TimeUnit.MINUTES);
Not sure if this is the best way of doing it, but is certainly one possible way of completing this task at a scheduled rate.
Solved this using SchedulingConfigurer, here's a sample:
#Configuration
#EnableScheduling
public class BatchConfig implements SchedulingConfigurer {
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(new Runnable() {
#Override
public void run() {
//run your code here
}
}, new Trigger() {
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
//extract cron from database
CronTrigger trigger = new CronTrigger(new CronTrigger(//insert cron that you got from database));
return trigger.nextExecutionTime(triggerContext);
}
});
}
}

how to run scheduler just after inserting data into database in spring boot?

I am new to spring boot and I have a requirement in which I have to run scheduler only if new data is inserted into table. Thanks for any help
Hibernate has an interceptor mecanism that allows you to get notified, at specific times, when database events occurs.
Such events are creation/deletion/flush of the session. As you get access to the objects being subject to the given event, you have a mean to fire a process when a given object of a given class (which you can easily map to a table in your schema) is modified.
The javadoc can be found here :
https://docs.jboss.org/hibernate/orm/4.0/manual/en-US/html/events.html
You can use the interceptor mecanism along with Java's ScheduledExecutorService to schedule a task when hibernate intercepts the save operation. You can create your business logic under that interceptor.
Scheduling is not enabled by default. Before adding any scheduled jobs we need to enable scheduling explicitly by adding the #enableScheduling annotation.
Now with the help of the ScheduledTaskRegistrar#addTriggerTask method, we can add a Runnable task and a Trigger implementation to recalculate the next execution time after the end of each execution.
#EnableScheduling
public class DynamicSchedulingConfig implements SchedulingConfigurer {
#Autowired
private TickService tickService;
#Bean
public Executor taskExecutor() {
return Executors.newSingleThreadScheduledExecutor();
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(
new Runnable() {
#Override
public void run() {
tickService.tick();
}
},
new Trigger() {
#Override
public Date nextExecutionTime(TriggerContext context) {
Optional<Date> lastCompletionTime =
Optional.ofNullable(context.lastCompletionTime());
Instant nextExecutionTime =
lastCompletionTime.orElseGet(Date::new).toInstant()
.plusMillis(tickService.getDelay());
return Date.from(nextExecutionTime);
}
}
)
}
}

Mixing Java `ExecutorService` with Spring `TaskExecutor`

I have a piece of Java code that I previously used without Spring that looks like this:
// `Callable` instead of `Runnable` because we need to throw exceptions
public MyTask extends Callable<Void> {
#Override
public Void call() throws Exception { ... }
}
public class MyTasksRunner {
private final ExecutorService executorService;
...
public void run() throws Exception {
List<MyTask> tasks = ...;
var futures = executorService.invokeAll(tasks);
for (var future : futures) {
// Rethrow any exceptions happened in the threads.
future.get();
}
}
}
Now I'm merging this code into a larger Spring Boot application that has async enabled. It configures a TaskExecutor, which doesn't have the same interface as ExecutorService. A TaskExecutor can only run Runnables, not Callables.
I can probably have a TaskExecutor bean for async Spring, and another ExecutorService bean for the MyTasksRunner code at the same time. But I wonder what options I have if I want to merge those:
Can I tell Spring to use an ExecutorService for its async stuff?
Can I convert my Callable code to use Runnables instead, while still being able to propagate exceptions from the tasks?
I also thought about just making MyTask a Spring component and annotating it with #Async, but I don't really like that it makes the MyTask* code tied to Spring.
Yes, you can convert your Callable task to Runnable as I see you don't expect any return value. But with one condition - you cant throw Checked Exception however you may continue throwing Runtime Exception.
Also, yes you can define Executor bean as below to inject ExecutorService
#Bean
public Executor taskExecutor() {
ExecutorService executor = Executors.newFixedThreadPool(2);
return executor;
}
If you dont define an Executor bean, Spring creates SimpleAsyncTaskExecutor and uses that.

How to pass a job object instance to quartz scheduler

What I want to be able to do is read a file with a bunch of batch files (and args) and create a quartz job for each of those entries. I know I'm missing something obvious, but I can't seem to find how to do this anywhere on google. Everything I'm finding says a new class has to be coded for each job which can't be externally constructed. I can't seem to find how to create an instance of a class which I can pass into the scheduler.
public class MyJob implements Job{
private String[] jobArgs = null;
public MyJob(String[] jobArgs){
this.jobArgs = jobArgs;
}
public void execute(JobExecutionContext arg0) throws JobExecutionException{
ExternalProcess ep - new ExternalProcess();
try{
ep.runExecutableCommand(jobargs);
}catch(Exception e){...}
}
}
public class JobScheduler {
...
List<String[]> jobArgList = loadJobListFromDisk();
List<MyJob> = new ArrayList<MyJob>();
for(String[] jobArgs : jobList){
MyJob myJob = new MyJob(jobArgs);
// Is it possible to pass in a reference to an instance somehow
// instead of letting the scheduler create the instance based on
// the class definition? I know this syntax doesn't work, but this
// is the general idea of what I'm trying to do.
JobDetail jobDetail = JobBuilder.newJob(myJob).withIdentity...
}
}
In quartz 2, you need to use a JobDataMap (stored in the jobDetail) to transfer parameters to the execute method. It will be available in the context.getJobDetail().getJobDataMap()
From what ive had quality time with Quartz Scheduler or rather JobBuilder, its written in such a dumb way it only accepts Class object as parameter. I was not able to find a way to pass Job Object into the builder.
It's a very very bad design, resulting in future problems with writing generic solutions for Quartz in version 1.X at least.

How to pass instance variables into Quartz job?

I wonder how to pass an instance variable externally in Quartz?
Below is pseudo code I would like to write. How can I pass externalInstance into this Job?
public class SimpleJob implements Job {
#Override
public void execute(JobExecutionContext context)
throws JobExecutionException {
float avg = externalInstance.calculateAvg();
}
}
you can put your instance in the schedulerContext.When you are going to schedule the job ,just before that you can do below:
getScheduler().getContext().put("externalInstance", externalInstance);
Your job class would be like below:
public class SimpleJob implements Job {
#Override
public void execute(JobExecutionContext context)
throws JobExecutionException {
SchedulerContext schedulerContext = null;
try {
schedulerContext = context.getScheduler().getContext();
} catch (SchedulerException e1) {
e1.printStackTrace();
}
ExternalInstance externalInstance =
(ExternalInstance) schedulerContext.get("externalInstance");
float avg = externalInstance.calculateAvg();
}
}
If you are using Spring ,you can actually using spring's support to inject the whole applicationContext like answered in the Link
While scheduling the job using a trigger, you would have defined JobDataMap that is added to the JobDetail. That JobDetail object will be present in the JobExecutionContext passed to the execute() method in your Job. So, you should figure out a way to pass your externalInstance through the JobDataMap. HTH.
Add the object to the JobDataMap:
JobDetail job = JobBuilder.newJob(MyJobClass.class)
.withIdentity("MyIdentity",
"MyGroup")
.build();
job.getJobDataMap()
.put("MyObject",
myObject);
Access the data from the JobDataMap:
var myObject = (MyObjectClass) context.getJobDetail()
.getJobDataMap()
.get("carrier");
Solve this problem by creating one interface with one HashMap putting required information there.
Implement this interface in your Quartz Job class this information will be accessible.
In IFace
Map<JobKey,Object> map = new HashMap<>();
In Job
map.get(context.getJobDetail().getKey()) => will give you Object
Quartz has a simple way to grep params from JobDataMap using setters
I am using Quartz 2.3 and I simply used setter to fetch passed instance objects
For example I created this class
public class Data implements Serializable {
#JsonProperty("text")
private String text;
#JsonCreator
public Data(#JsonProperty("b") String text) {this.text = text;}
public String getText() {return text;}
}
Then I created an instance of this class and put it inside the JobDataMap
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("data", new Data(1, "One!"));
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group")
.withDescription("bla bla bla")
.usingJobData(jobDataMap) // <!!!
.build();
And my job class looks like this
public class HelloJob implements Job {
Data data;
public HelloJob() {}
public void execute(JobExecutionContext context)
throws JobExecutionException
{
String text = data.getText();
System.out.println(text);
}
public void setData(Data data) {this.data = data;}
}
Note: it is mandatory that the field and the setter matched the key
This code will print One! when you schedule the job.
That's it, clean and efficient
This is the responsibility of the JobFactory. The default PropertySettingJobFactory implementation will invoke any bean-setter methods, based on properties found in the schdeuler context, the trigger, and the job detail. So as long as you have implemnted an appropriate setContext() setter method you should be able to do any of the following:
scheduler.getContext().put("context", context);
Or
Trigger trigger = TriggerBuilder.newTrigger()
...
.usingJobData("context", context)
.build()
Or
JobDetail job = JobBuilder.newJob(SimpleJob.class)
...
.usingJobData("context", context)
.build()
Or if that isn't enough you can provide your own JobFactory class which instantiates the Job objects however you please.

Categories

Resources