runtime rescheduling #scheduled cron expression without restarting app - java

I have used scheduled annotation to schedule the task reading cronexpression from db. But when we change cron expression using UI , it needs to restart the app. How to inject TaskScheduler and ScheduledFuture or any other approach to reschedule without restarting when user update from UI which will call this method updateTestJobSchedule . Any example as per below code will be much helpful.
Currently i have : -
#Configuration
public class BasicConfig {
#Autowired
private TestJobSchedulerRepository testJobSchedulerRepository;
#Bean
public String getCronExpressionFromDb(){
return testJobSchedulerRepository.findByIsActive(1).getCronExpression();
}
}
#RestController
#EnableScheduling
#RequestMapping("/api")
public class TestJobController {
#Scheduled(cron="#{getCronExpressionFromDb}")
public void doTestJob(){
// does the job
}
// This update cron expression when request comes from UI
#RequestMapping(value = "/update-testjob-schedule_application",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> updateTestJobSchedule(#RequestBody
TestJobScheduler testJobScheduler) throws URISyntaxException {
if(testJobScheduler.getIsActive() == 0){
testJobScheduler.setIsActive(1);
} else{
testJobScheduler.setIsActive(0);
}
testJobSchedulerRepository.save(testJobScheduler);
return new ResponseEntity<>("{\"status\":\"success\"}", HttpStatus.OK);
}
}

Your best bet is to use the TaskScheduler object. The taskScheduler bean is automatically created when you have #EnableScheduling on a class annotated with #Configuration. This object can be used to schedule job on the fly. Here a quick exemple of what you can do with it :
#Configuration
#EnableScheduling
public class CronJobConfiguration implements CommandLineRunner {
#Autowired
private TestJobSchedulerRepository testJobSchedulerRepository;
#Autowired
private TaskScheduler taskScheduler;
#Override
public void run(String... strings) {
String cronExpression = testJobSchedulerRepository.findByIsActive(1).getCronExpression();
Trigger trigger = new CronTrigger(cronExpression);
ScheduledFuture scheduledFuture = taskScheduler
.schedule(() -> {/* the job you want to run*/}, trigger);
/* Keep the scheduledFuture to be able to cancel the job later Ex : */
scheduledFuture.cancel(true);
}
}
You'll probably need to keep the ScheduledFuture object returned by the schedule method to be able to cancel the job at some point. Also you'll need to have a service that use the TaskScheduler so that you can schedule job when the updateTestJobSchedule is called.

Related

How can I dynamically create Triggers in Spring Boot using Quartz?

I have a code like this:
#Component
public class SampleJob implements Job {
#Autowired
private SampleTask sampleTask;
#Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
sampleTask.imprimir();
}
#Bean
public JobDetail jobDetail(){
return JobBuilder.newJob().ofType(SampleJob.class)
.storeDurably()
.withIdentity("Quartz_Job_Detail")
.withDescription("Estoy invocando el job de ejempo")
.build();
}
#Bean
public Trigger trigger(JobDetail jobDetail){
return TriggerBuilder.newTrigger().forJob(jobDetail)
.withIdentity("Quartz_Trigger")
.withDescription("Trigger de ejemplo")
.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInMilliseconds(5000))
.build();
}
}
In this case imprimir() is a function that only prints a text to know that quartz is working. The above code creates a trigger that run every 5 seconds. Now, what I want to do is to create multiple triggers based in the elements of a list taken from a database. Each Triggers has to have different Scheduled time. How can I create multiple triggers with different trigger times using a loop or something?
You just need to create a durable job once and then create all triggers for that job.
#Service
class QuartzService {
#Autowired
private SchedulerFactoryBean schedulerFactoryBean;
void init(List<MyTrigger> triggers) {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.addJob(job(), true);
for (MyTrigger myTrigger: triggers) {
scheduler.scheduleJob(trigger(myTrigger));
}
}
private JobDetail job() {
return JobBuilder.newJob(SampleJob.class)
.withIdentity("QuartzJob", "QuartzJob")
.storeDurably()
.build();
}
private Trigger trigger(MyTrigger trigger) {
return TriggerBuilder.newTrigger()
.withIdentity(trigger.name)
.withSchedule(
CronScheduleBuilder.cronSchedule(trigger.schedule))
.forJob("QuartzJob", "QuartzJob")
.build();
}
}
class MyTrigger {
String name;
String schedule;
}
So, here you only need to pass a List of MyTrigger to QuartsService.init() method and it will create dynamically the triggers with its own schedule.

Run task few seconds/minutes/hours later after a call

Does Spring Boot have something that allows to run a task several minutes later after a call?
Update:
Found that Spring Boot has ThreadPoolTaskScheduler. That allows to implement one time task execution with a time delay. See the answer below
First, we need to define a Bean
#Bean
public ThreadPoolTaskScheduler getScheduler(){
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
return scheduler;
}
The task that needs to be executed
public class WakeUpCallTask implements Runnable {
private String parameter1;
private String parameter2;
public WakeUpCallTask(String parameter1, String parameter2) {
super();
this.parameter1= parameter1;
this.parameter2= parameter2;
}
#Override
public void run() {
// Code that should be executed
}
}
Endpoint example - scheduling a task in 10000ms
#Autowired
ThreadPoolTaskScheduler scheduler;
#PostMapping(value = "/wake-me-up", consumes = "application/json")
#ResponseStatus(HttpStatus.OK)
public void scheduleCall(#RequestBody WakeMeUpRequest request) {
scheduler.schedule(
new WakeUpCallTask(request.getParameter1(), request.getParameter2()),
new Date(System.currentTimeMillis() + 10000));
}

Spring Boot - #Async is ignored

Using Spring Boot 1.5.2.RELEASE and the #Async annotation seems to be ignored.
Have setup the environment like this:
#SpringBootApplication
#EnableAsync
public class Application extends AsyncConfigurerSupport {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("async-task-");
executor.initialize();
return executor;
}
...
... the async method itself:
#Service
public class MyService {
#Async
public Future<Long> asyncTask() throws Exception {
Long test = 1023L;
Thread.sleep(10000);
return new AsyncResult<>(test);
}
}
... now I'm trying to use this:
#RestController
public MyController {
#Autowired
public MyService myService;
#PostMapping("/test")
public ResponseEntity<MyResponse> test() {
return new ResponseEntity<>(
new MyResponse(myService
.asyncTask()
.get()),
HttpStatus.OK);
}
}
... and the controller method still hangs for 10sec instead of to be immediatelly returned.
The #Async method is called from the different object. It's neither private nor transactional one as it mentioned at the similar questions.
How to let the method to be invoked asynchronously?
You should have a look at the
Future#get javadoc:
Waits if necessary for the computation to complete, and then retrieves
its result.
You are transforming your async method to a synchronous call by calling get method.
Thus instead of calling get, just return the Future. Spring MVC supports future as return type:
A ListenableFuture or CompletableFuture/CompletionStage can
be returned when the application wants to produce the value from a
thread pool submission.
Example:
return myService.asyncTask().thenApply(r -> ResponseEntity.ok(new MyResponse(r)));
Your test() function is invoking get() on the Future instance. The documentation for this function states: "Waits if necessary for the computation to complete, and then retrieves its result."
So rather than invoking get(), you likely want to return some sort if ID that the caller can use to retrieve the result at a later time (or switch to a synchronous response).

Spring Boot: Using a #Service in Quartz job execution

In an application, since I converted it from a classical Spring webapp (deployed in a system Tomcat) to a Spring Boot (V1.2.1) application I face the problem that the Quartz-based scheduled jobs are not working anymore.
I schedule these Quartz jobs like this:
// My own Schedule object which holds data about what to schedule when
Schedule schedule = scheduleService.get(id of the schedule);
String scheduleId = schedule.getId();
JobKey jobKey = new JobKey(scheduleId);
TriggerKey triggerKey = new TriggerKey(scheduleId);
JobDataMap jobData = new JobDataMap();
jobData.put("scheduleId", scheduleId);
JobBuilder jobBuilder = JobBuilder.newJob(ScheduledActionRunner.class)
.withIdentity(jobKey)
.withDescription(schedule.getName())
.usingJobData(jobData);
JobDetail job = jobBuilder.build();
TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger()
.forJob(jobKey)
.withIdentity(triggerKey)
.withDescription(schedule.getName());
triggerBuilder = triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(schedule.toCronExpression()));
Trigger trigger = triggerBuilder.build();
org.quartz.Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.scheduleJob(job, trigger);
ScheduledActionRunner:
#Component
public class ScheduledActionRunner extends QuartzJobBean {
#Autowired
private ScheduleService scheduleService;
public ScheduledActionRunner() {
}
#Override
public void executeInternal(final JobExecutionContext context) throws JobExecutionException {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
final JobDataMap jobDataMap = context.getMergedJobDataMap();
final String scheduleId = jobDataMap.getString("scheduleId");
final Schedule schedule = scheduleService.get(scheduleId);
// here it goes BANG since scheduleService is null
}
}
ScheduleService is a classical Spring service which fetches data from Hibernate.
As I said above, this worked fine until I moved to Spring Boot.
When I implemented this code with the classical Spring application, SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); did the trick to take care of autowiring the service.
What is needed to make this work again in the Spring Boot environment ?
Edit:
At the end I chose to move away from using Quartz in favour of Spring's ThreadPoolTaskScheduler.The code was much simplified and it works as expected.
The SpringBeanAutowiringSupport uses the web application context, which is not available in your case. If you need a spring managed beans in the quartz you should use the quartz support provided by spring. This will give you full access to all the managed beans.
For more info see the quartz section at spring docs at http://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html. Also see following example of usage quartz with spring managed beans. Example is based on your code.
So you can change the first code snippet (where the quartz initialization is done) with follwoing spring alternatives.
Create job detail factory
#Component
public class ScheduledActionRunnerJobDetailFactory extends JobDetailFactoryBean {
#Autowired
private ScheduleService scheduleService;
#Override
public void afterPropertiesSet() {
setJobClass(ScheduledActionRunner.class);
Map<String, Object> data = new HashMap<String, Object>();
data.put("scheduleService", scheduleService);
setJobDataAsMap(data);
super.afterPropertiesSet();
}
}
Create the trigger factory
#Component
public class ActionCronTriggerFactoryBean extends CronTriggerFactoryBean {
#Autowired
private ScheduledActionRunnerJobDetailFactory jobDetailFactory;
#Value("${cron.pattern}")
private String pattern;
#Override
public void afterPropertiesSet() throws ParseException {
setCronExpression(pattern);
setJobDetail(jobDetailFactory.getObject());
super.afterPropertiesSet();
}
}
And finally create the SchedulerFactory
#Component
public class ActionSchedulerFactoryBean extends SchedulerFactoryBean {
#Autowired
private ScheduledActionRunnerJobDetailFactory jobDetailFactory;
#Autowired
private ActionCronTriggerFactoryBean triggerFactory;
#Override
public void afterPropertiesSet() throws Exception {
setJobDetails(jobDetailFactory.getObject());
setTriggers(triggerFactory.getObject());
super.afterPropertiesSet();
}
}
My answer not fully matches to you question, but Spring expose you another ability - to start cron-expression based scheduler on any service.
Using Spring.Boot you can configure your application to use scheduler by simple placing
#EnableScheduling
public class Application{
....
After that just place following annotation on public(!) method of #Service
#Service
public class MyService{
...
#Scheduled(cron = "0 * * * * MON-FRI")
public void myScheduledMethod(){
....
}

Creating Spring Framework task programmatically?

I need to create task on the fly in my app. How can I do that? I can get scheduler with #autowired annotation, but scheduler takes Runnable objects. I need to give Spring objects, so that my tasks can use #autowired annotation too.
#Autowired private TaskScheduler taskScheduler;
You just need to wrap your target object in a Runnable, and submit that:
private Target target; // this is a Spring bean of some kind
#Autowired private TaskScheduler taskScheduler;
public void scheduleSomething() {
Runnable task = new Runnable() {
public void run() {
target.doTheWork();
}
};
taskScheduler.scheduleWithFixedDelay(task, delay);
}

Categories

Resources