Spring: endpoint to start a scheduled task - spring-boot

I have a scheduled task that works perfectly, like this:
#Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should execute on weekdays only
}
I want to create a REST endpoint that would start this task out of it's normal schedule.
How would I programatically fire-and-forget this task?

You could do something really simple.
Your schedule:
#Component
#RequiredArgsConstructor
public class MySchedule {
private final MyClassThatHasTheProcessing process;
#Scheduled(cron = "*/5 * * * * MON-FRI")
public void doSomething() {
// the actual process is made by the method doHeavyProcessing
process.doHeavyProcessing();
}
}
Your Controller
#RestController
#RequestMapping(path = "/task")
#RequiredArgsConstructor
public class MyController {
private final MyClassThatHasTheProcessing process;
// the executor used to asynchronously execute the task
private final ExecutorService executor = Executors.newSingleThreadExecutor();
#PostMapping
public ResponseEntity<Object> handleRequestOfStartTask() {
// send the Runnable (implemented using lambda) for the ExecutorService to do the async execution
executor.execute(() - >{
process.doHeavyProcessing();
});
// the return happens before process.doHeavyProcessing() is completed.
return ResponseEntity.accepted().build();
}
}
This will keep your scheduled task working as well as being able do trigger the task on demand by hitting your endpoint.
The HTTP 202 Accepted will be returned and the actual thread released, while the ExecutorService will delegate the process.doHeavyProcessing execution to another thread, which means that it will run in a 'fire and forget' style, because the thread that is serving the request will return even before the other task is finally terminated.
If you don't know what is an ExecutorService, this may help.

This can be done by writing something like below
#Controller
MyController {
#Autowired
MyService myService;
#GetMapping(value = "/fire")
#ResponseStatus(HttpStatus.OK)
public String fire() {
myService.fire();
return "Done!!";
}
}
MyService {
#Async
#Scheduled(cron="*/5 * * * * MON-FRI")
public void fire(){
// your logic here
}
}

Related

Spring Boot EventListener always on main thread

I have been trying to create an async listener that would execute after my request has been terminated which mean my transaction has been committed. Unfortunately i was not able to make it happen it is always part of the main thread. Which mean my request would never END before the async methods is finishing. It is basically for creating a webhook service that would send an http request when the request going through my system end and the transaction is committed. Does anyone had a similar issue ?
#Component
#RequiredArgsConstructor
public class EventListenerAsync {
#PostPersist
public void postPersist(final Event event) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
#Override
public void afterCompletion(final int status) {
if (status == TransactionSynchronization.STATUS_COMMITTED) {
// call #async public method from another class
}
}
});
}
#TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void processEventAsynce(final Event event) {
// call #async public method from another class
}
}
Thanks a lot for you help.

Schedule multiple tasks in Spring

I have 2 Tasks which implements (my) PeriodicTask which implements Runnable.
There is a code which works fine. But I expects plenty of Tasks in future and want to avoid registering each of them in TaskScheduler.
Is it possible TaskScheduler find all classes (which implement PeriodicTask) and schedule them.
There is also a problem how to set schedule params (let them be static final for beginning).
#Component
public class TaskScheduler {
private final Task1 task1;
private final Task2 test2;
public TaskScheduler(Task1 task1, Task2 test2) {
this.task1 = task1;
this.test2 = test2;
}
#Scheduled(fixedDelay = 60 * 1000, initialDelay = 1000)
public void scheduleTask1() {task1.run();}
#Scheduled(fixedDelay = 3600 * 1000, initialDelay = 5 * 1000)
public void scheduleTask2() {test2.run();}
}
You can implement it without #Scheduled annotation:
#Component
public class TaskSchedulerComponent {
#Autowired
private ThreadPoolTaskScheduler taskScheduler;
public void schedulePeriodicTask(Runnable task, long delay, long period){
PeriodicTrigger periodicTrigger
= new PeriodicTrigger(period, TimeUnit.MICROSECONDS);
periodicTrigger.setFixedRate(true);
periodicTrigger.setInitialDelay(delay);
taskScheduler.schedule(task,periodicTrigger);
}
}
Then in your main method you register all tasks, which should be executed periodically, with given delay and time period.
Note:
To make #Scheduled to use parameters, you can use values from property file like this:
#Scheduled(fixedDelayString = "${fixed.delay}",initialDelay = ${init.delay})

Running async jobs in dropwizard, and polling their status

In dropwizard, I need to implement asynchronous jobs and poll their status.
I have 2 endpoints for this in resource:
#Path("/jobs")
#Component
public class MyController {
#POST
#Produces(MediaType.APPLICATION_JSON)
public String startJob(#Valid MyRequest request) {
return 1111;
}
#GET
#Path("/{jobId}")
#Produces(MediaType.APPLICATION_JSON)
public JobStatus getJobStatus(#PathParam("id") String jobId) {
return JobStatus.READY;
}
}
I am considering to use quartz to start job, but only single time and without repeating. And when requesting status, I will get trigger status. But the idea of using quartz for none-scheduled usage looks weird.
Is there any better approaches for this? Maybe dropwizard provides better tools itself? Will appriciate any advices.
UPDATE: I also looking at https://github.com/gresrun/jesque, but can not find any way to poll the status of running job.
You can use the Managed interface. In the snippet below I am using the ScheduledExecutorService to exuecute jobs, but you can use Quartz instead if you like. I prefer working with ScheduledExecutorService as it is simpler and easier...
first step is to register your managed service.
environment.lifecycle().manage(new JobExecutionService());
Second step is to write it.
/**
* A wrapper around the ScheduledExecutorService so all jobs can start when the server starts, and
* automatically shutdown when the server stops.
* #author Nasir Rasul {#literal nasir#rasul.ca}
*/
public class JobExecutionService implements Managed {
private final ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
#Override
public void start() throws Exception {
System.out.println("Starting jobs");
service.scheduleAtFixedRate(new HelloWorldJob(), 1, 1, TimeUnit.SECONDS);
}
#Override
public void stop() throws Exception {
System.out.println("Shutting down");
service.shutdown();
}
}
and the job itself
/**
* A very simple job which just prints the current time in millisecods
* #author Nasir Rasul {#literal nasir#rasul.ca}
*/
public class HelloWorldJob implements Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* #see Thread#run()
*/
#Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}
As mentioned in the comment below, if you use Runnable, you can Thread.getState(). Please refer to Get a List of all Threads currently running in Java. You may still need some intermediary pieces depending on how you're wiring you application.

How to dynamically schedule a Spring Batch job with ThreadPoolTaskScheduler

I have a Spring Batch application in which I want to schedule jobs calls.
The scheduling interval is not known at build so I can't just annotate my Job with #Scheduled.This led me to use a ThreadPoolTaskScheduler.
The thing is the method schedule takes a Runnable as a parameter. Is it possible to schedule jobs this way ?
I can call the job directly from the following service but I can't schedule it.
Here is my the background of my problem, I tried to make it simple :
#Service
public class ScheduledProcessor{
private final ThreadPoolTaskScheduler threadPoolTaskScheduler;
private Application application;
#Autowired
public ScheduledProcessor(ThreadPoolTaskScheduler threadPoolTaskScheduler, Application application){
this.threadPoolTaskScheduler = threadPoolTaskScheduler;
this.application = application;
scheduledTasks = new ArrayList();
Trigger trigger = new CronTrigger("0/6 * * * * *");
//Here I am trying to schedule my job.
//The following line is wrong because a Job can't be cast to a Runnable but I wanted to show the intended behaviour.
threadPoolTaskScheduler.schedule((Runnable) application.importUserjob, trigger);
System.out.println("Job launch !");
}
And here is the JobBuilderFactory :
#Bean
public Job importUserJob(JobBuilderFactory jobs, Step s1, Step s2) {
return jobs.get("importUserJob")
.incrementer(new RunIdIncrementer())
.flow(s1)
.end()
.build();
}
I understand (well, I'm even not sure about that) that I can't directly cast a Job to a Runnable but is it possible to convert it in any way ? Or can you give me some advice about what to use for being able to dynamically schedule spring batch jobs ?
In case that changes something, I also need to be able to restart / skip my steps, as I currently can with the threadPoolTaskScheduler.
Thank you in advance for any help or hint you could provide.
I finally got how to do it !
I created a class which implements Runnable (and for convenience, extends Thread, which avoid the need to implement all of Runnable classes).
#Component
public class MyRunnableJob extends Thread implements Runnable{
private Job job;
private JobParameters jobParameters;
private final JobOperator jobOperator;
#Autowired
public MyRunnableJob(JobOperator jobOperator) {
this.jobOperator = jobOperator;
}
public void setJob(Job job){
this.job=job;
}
#Override
public void run(){
try {
String dateParam = new Date().toString();
this.jobParameters = new JobParametersBuilder().addString("date", dateParam).toJobParameters();
System.out.println("jobName : "+job.getName()+" at "+dateParam);
jobOperator.start(job.getName(), jobParameters.toString());
} catch (NoSuchJobException | JobInstanceAlreadyExistsException | JobParametersInvalidException ex) {
Logger.getLogger(MyRunnableJob.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
In my ScheduledProcessor class, I set a Job to myRunnable class and then pass it as a parameter of the schedule method.
public class SchedulingProcessor {
//Autowired fields :
private final JobLauncher jobLauncher;
private final Job importUserJob;
private final ThreadPoolTaskScheduler threadPoolTaskScheduler;
private final MyRunnableJob myRunnableJob;
//Other fields :
private List<ScheduledFuture> scheduledTasks;
#Autowired
public SchedulingProcessor(JobLauncher jobLauncher, Job importUserJob, ThreadPoolTaskScheduler threadPoolTaskScheduler, MyRunnableJob myRunnableJob) throws Exception {
this.jobLauncher=jobLauncher;
this.importUserJob=importUserJob;
this.threadPoolTaskScheduler=threadPoolTaskScheduler;
this.myRunnableJob=myRunnableJob;
Trigger trigger = new CronTrigger("0/6 * * * * *");
myRunnableJob.setJob(this.importUserJob);
scheduledTasks = new ArrayList();
scheduledTasks.add(this.threadPoolTaskScheduler.schedule((Runnable) myRunnableJob, trigger));
}
}
The scheduledTasks list is just to keep a control over the tasks I just scheduled.
This trick enabled me to dynamically (thanks to ThreadPoolTaskScheduler) schedule Spring Batch Jobs encapsulated in a class implementing Runnable. I wish it can help someone in the same case as mine.
Heres another way to trigger them from your spring context.
Job emailJob = (Job) applicationContext.getBean("xyzJob");
JobLauncher launcher = (JobLauncher) applicationContext
.getBean("jobLauncher");
launcher.run(emailJob, new JobParameters());

Nullpointer injecting a bean when creating a job via quartz

The context is the next:
I have a web app using Spring 2.5 and Struts 1.1
I create a job dynamically in an Action using Quartz:
JobDetailBean jobDetail = new JobDetailBean();
jobDetail.setBeanName("foo");
Map<String, String> map = new HashMap<String,String>();
map.put("idFeed","foo");
map.put("idSite","foo");
jobDetail.setJobDataAsMap(map);
jobDetail.setJobClass(FeedJob.class);
jobDetail.afterPropertiesSet();
CronTriggerBean cronTrigger = new CronTriggerBean();
cronTrigger.setBeanName("foo");
String expression = " * * * * * *";
cronTrigger.setCronExpression(expression);
cronTrigger.afterPropertiesSet();
// add to schedule
scheduler.scheduleJob((JobDetail) jobDetail, cronTrigger);
scheduler is a org.quartz.Scheduler injected in the Action.
The class FeedJob has the method executeInternal(JobExecutionContext ctx) which is the code the job has to run:
public class FeedJob extends QuartzJobBean {
private FeedBL feedBL;
public void setFeedBL(FeedBL feedBL) {this.feedBL = feedBL;}
public FeedJob() {}
public String idFeed;
public String idSite;
public String getIdFeed() {
return idFeed;
}
public void setIdFeed(String idFeed) {
this.idFeed = idFeed;
}
public String getIdSite() {
return idSite;
}
public void setIdSite(String idSite) {
this.idSite = idSite;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
try {
feedBL.sincronizacionProductFeed(idFeed, idSite);
} catch (Exception e) {
e.printStackTrace();
}
}
}
And when its going to run, I get a java.lang.NullPointerException when trying to run this line of code:
feedBL.sincronizacionProductFeed(idFeed, idSite);
The reason is when I'm creating the job in the Action I'm setting the job:
jobDetail.setJobClass(FeedJob.class);
And Spring doesn't notice about the bean he has already created, so that instance of the FeedJob class hasn't god injected the feedBL class.
Any good idea for solving this problem?
I have tried to give the job the context like this:
jobDetail.setApplicationContext(applicationContext);
But doesnt work.
You may want to check this answer. It solves the same problem you are experiencing.

Resources