execute spring job with scheduler except holidays - spring

I have a java job configured with #EnableScheduling and I am able to run job with annotation
#Scheduled(cron = "${job1.outgoingCron:0 45 15,18,20 * * MON-FRI}", zone = "America/New_York")
public void Process() {
}
But I don't want to run this job for US holidays what is the best way to update schedule.

Try to set your schedule explicitly, using CronTrigger or custom Trigger, for example:
#SpringBootApplication
#EnableScheduling
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Bean
public TaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
}
#Component
#RequiredArgsConstructor // Lombok annotation
public class StartUp implements ApplicationRunner {
#NonNull private final TaskScheduler scheduler;
#Override
public void run(ApplicationArguments args) throws Exception {
// Variant 1
scheduler.schedule(this::myTask, new CronTrigger(/* your schedule */));
// Variant 2
scheduler.schedule(this::myTask, this::myTriger);
}
private void myTask() {
//...
}
private Date myTrigger(TriggerContext triggerContext) {
//...
}
}

Related

How to schedule a cron job in spring boot without using #Scheduled() annotation

In spring boot, can I schedule a spring job by not using #Scheduled annotation to a method?
I am working with spring job in the spring boot. I want to schedule a job by using cron expression, but without using #Scheduled(cron = " ") annotation to the method.
I know that I can schedule a job inside this method as below.
#Scheduled (cron = "0 10 10 10 * ?")
public void execute() {
/ * some job code * /
}
But I want it to be dynamic so that I can take a cron expression as input from the user and schedule it.
I came up with a working example since I found your question interesting and have been interested in this problem before. It's based entirely on the source code so I have no idea if it comes close to following best practice. Nonetheless, you may be able to tune it to your needs. FYI, you don't necessarily need to create a new ScheduledTaskRegistrar object - I figured that since your objective is a dynamic scheduler, you wouldn't be interested in defining your tasks purely in the overwritten method.
#SpringBootApplication
public class TaskScheduler implements SchedulingConfigurer, CommandLineRunner {
public static void main(String[] args){SpringApplication.run(TaskScheduler.class, args);}
List<CronTask> cronTasks;
#Override
public void run(String... args) throws Exception {
CronTask task = this.createCronTask(new Runnable() {
#Override
public void run() {
System.out.println(LocalDateTime.now());
}
}, "1/10 * * * * *");
ScheduledTaskRegistrar taskRegistrar = new ScheduledTaskRegistrar();
taskRegistrar.addCronTask(task);
configureTasks(taskRegistrar);
Thread.sleep(51);
taskRegistrar.destroy();
taskRegistrar = null;
ScheduledTaskRegistrar taskRegistrar2 = new ScheduledTaskRegistrar();
taskRegistrar2.addCronTask(task);
configureTasks(taskRegistrar2);
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// "Calls scheduleTasks() at bean construction time" - docs
taskRegistrar.afterPropertiesSet();
}
public CronTask createCronTask(Runnable action, String expression) {
return new CronTask(action, new CronTrigger(expression));
}
}
I have experience using cron jobs in Azure and other places. Programming in Java, I have typically used #Scheduled with fixed times just for the sake of simplicity. Hope this is useful to you though.
Here is my working example, If somebody wants to use TaskScheduler without using #Scheduled Annotation
#Configuration Class
#Configuration
public class SchedulerConfig implements SchedulingConfigurer {
final Logger LOGGER = LogManager.getLogger(SchedulerConfig.class);
#Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
LOGGER.debug("Creating Async Task Scheduler");
scheduledTaskRegistrar.setTaskScheduler(taskScheduler());
}
// This is mandatory otherwise it will to be able to find bean of
// taskScheduler. Without this it was giving runtime error says, can not find
// taskScheduler bean.
#Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(20); // Better to read it from property file.
scheduler.setThreadNamePrefix("ThreadScheduler-");
scheduler.initialize();
return scheduler;
}
}
Scheduler Class which is called from Application class.
#Component
public class MyTaskScheduler {
private TaskScheduler taskScheduler;
// Here we are auto-wiring taskScheduler, that's why need to create
// taskScheduler bean in configuration class
#Autowired
public void setScheduler(TaskScheduler scheduler) {
this.taskScheduler = scheduler;
}
public void schedule() {
taskScheduler.scheduleWithFixedDelay(new Runnable(){
#Override
public void run() {
System.out.println("I am running after every 1 second");
}
}, 1000);
}
}
If in any chance #Configuration annotation is not working so put #EnableConfigurationProperties at main running class.
Make sure that you put #EnableScheduling at Application class, so main runnable class will look like
#SpringBootApplication
#EnableScheduling
#EnableConfigurationProperties
public class MainApplication implements CommandLineRunner {
#Autowired
MyTaskScheduler myTaskScheduler;
public static void main(String[] args) {
final Logger logger = LogManager.getLogger(MainApplication.class);
SpringApplication.run(MainApplication.class, args);
logger.info("Application started");
}
#Override
public void run(String... args) throws Exception {
myTaskScheduler.schedule();
}
}
This answer is similar to the two previous ones, but is more compact because it leverages the scheduling registrar already provided in the application context:
#Configuration
#EnableScheduling
public class Schedule implements SchedulingConfigurer {
private final transient WorkflowTriggerService workflowTriggerService;
public Schedule(final WorkflowTriggerService workflowTriggerService) {
this.workflowTriggerService = workflowTriggerService;
}
#Override
public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
for (final WorkflowTrigger trigger : workflowTriggerService.getWorkflowTriggersWithSchedules()) {
taskRegistrar.addCronTask(new WorkflowTask(trigger), trigger.getSchedule());
}
}
}
Each WorkflowTrigger returned by the service has its own cron schedule, and therefore allows dynamic registration of scheduled tasks that are not known at compilation time.

#RefreshScope stops #Scheduled task

I have a monitoring app wherein I am running a fixedRate task. This is pulling in a config parameter configured with Consul. I want to pull in updated configuration, so I added #RefreshScope. But as soon as I update the config value on Consul, the fixedRate task stops running.
#Service
#RefreshScope
public class MonitorService {
#Autowired
private AppConfig appConfig;
#PostConstruct
public void postConstRun() {
System.out.println(appConfig.getMonitorConfig());
}
#Scheduled(fixedRate = 1000)
public void scheduledMonitorScan() {
System.out.println("MonitorConfig:" + appConfig.getMonitorConfig());
}
}
AppConfig class just has a single String parameter:
#Configuration
#Getter
#Setter
public class AppConfig {
#Value("${monitor-config:default value}")
private String monitorConfig;
}
As soon as I update the value in consul, the scheduled task just stops running (display in sheduledMonitorScan method) stop showing up.
I'm successfully get & override the values from consul config server using RefreshScopeRefreshedEvent
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
#Component
#RefreshScope
public class AlertSchedulerCron implements ApplicationListener<RefreshScopeRefreshedEvent> {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
#Value("${pollingtime}")
private String pollingtime;
/*
* #Value("${interval}") private String interval;
*/
#Scheduled(cron = "${pollingtime}")
//#Scheduled(fixedRateString = "${interval}" )
public void task() {
System.out.println(pollingtime);
System.out.println("Scheduler (cron expression) task with duration : " + sdf.format(new Date()));
}
#Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
// TODO Auto-generated method stub
}
}
Here's how we've solved this issue.
/**
* Listener of Spring's lifecycle to revive Scheduler beans, when spring's
* scope is refreshed.
* <p>
* Spring is able to restart beans, when we change their properties. Such a
* beans marked with RefreshScope annotation. To make it work, spring creates
* <b>lazy</b> proxies and push them instead of real object. The issue with
* scope refresh is that right after refresh in order for such a lazy proxy
* to be actually instantiated again someone has to call for any method of it.
* <p>
* It creates a tricky case with Schedulers, because there is no bean, which
* directly call anything on any Scheduler. Scheduler lifecycle is to start
* few threads upon instantiation and schedule tasks. No other bean needs
* anything from them.
* <p>
* To overcome this, we had to create artificial method on Schedulers and call
* them, when there is a scope refresh event. This actually instantiates.
*/
#RequiredArgsConstructor
public class RefreshScopeListener implements ApplicationListener<RefreshScopeRefreshedEvent> {
private final List<RefreshScheduler> refreshSchedulers;
#Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
refreshSchedulers.forEach(RefreshScheduler::materializeAfterRefresh);
}
}
So, we've defined an interface, which does nothing in particular, but allows us to call for a refreshed job.
public interface RefreshScheduler {
/**
* Used after refresh context for scheduler bean initialization
*/
default void materializeAfterRefresh() {
}
}
And here is actual job, whose parameter from.properties can be refreshed.
public class AJob implements RefreshScheduler {
#Scheduled(cron = "${from.properties}")
public void aTask() {
// do something useful
}
}
UPDATED:
Of course AJob bean must be marked with #RefreshScope in #Configuration
#Configuration
#EnableScheduling
public class SchedulingConfiguration {
#Bean
#RefreshScope
public AJob aJob() {
return new AJob();
}
}
I have done workaround for this kind of scenario by implementing SchedulingConfigurer interface.
Here I am dynamically updating "scheduler.interval" property from external property file and scheduler is working fine even after actuator refresh as I am not using #RefreshScope anymore.
Hope this might help you in your case also.
public class MySchedulerImpl implements SchedulingConfigurer {
#Autowired
private Environment env;
#Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(10);
}
#Override
public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(this.taskExecutor());
taskRegistrar.addTriggerTask(() -> {
//put your code here that to be scheduled
}, triggerContext -> {
final Calendar nextExecutionTime = new GregorianCalendar();
final Date lastActualExecutionTime = triggerContext.lastActualExecutionTime();
if (lastActualExecutionTime == null) {
nextExecutionTime.setTime(new Date());
} else {
nextExecutionTime.setTime(lastActualExecutionTime);
nextExecutionTime.add(Calendar.MILLISECOND, env.getProperty("scheduler.interval", Integer.class));
}
return nextExecutionTime.getTime();
});
}
}
My solution consists of listening to EnvironmentChangeEvent
#Configuration
public class SchedulingSpringConfig implements ApplicationListener<EnvironmentChangeEvent>, SchedulingConfigurer {
private static final Logger LOGGER = LoggerFactory.getLogger(SchedulingSpringConfig.class);
private final DemoProperties demoProperties;
public SchedulingSpringConfig(DemoProperties demoProperties) {
this.demoProperties = demoProperties;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
LOGGER.info("Configuring scheduled task with cron expression: {}", demoProperties.getCronExpression());
taskRegistrar.addTriggerTask(triggerTask());
taskRegistrar.setTaskScheduler(taskScheduler());
}
#Bean
public TriggerTask triggerTask() {
return new TriggerTask(this::work, cronTrigger());
}
private void work() {
LOGGER.info("Doing work!");
}
#Bean
#RefreshScope
public CronTrigger cronTrigger() {
return new CronTrigger(demoProperties.getCronExpression());
}
#Bean
public ThreadPoolTaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
#Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (event.getKeys().contains("demo.config.cronExpression")) {
ScheduledTasksRefresher scheduledTasksRefresher = new ScheduledTasksRefresher(triggerTask());
scheduledTasksRefresher.afterPropertiesSet();
}
}
}
Then I use the ContextLifecycleScheduledTaskRegistrar to recreate the task.
public class ScheduledTasksRefresher extends ContextLifecycleScheduledTaskRegistrar {
private final TriggerTask triggerTask;
ScheduledTasksRefresher(TriggerTask triggerTask) {
this.triggerTask = triggerTask;
}
#Override
public void afterPropertiesSet() {
super.destroy();
super.addTriggerTask(triggerTask);
super.afterSingletonsInstantiated();
}
}
Properties definition:
#ConfigurationProperties(prefix = "demo.config", ignoreUnknownFields = false)
public class DemoProperties {
private String cronExpression;
public String getCronExpression() {
return cronExpression;
}
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
}
Main definition:
#SpringBootApplication
#EnableConfigurationProperties(DemoProperties.class)
#EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Based on previous answers I added the following interface and used it on #RefreshScope annotated beans:
public interface RefreshScopeScheduled {
#EventListener(RefreshScopeRefreshedEvent.class)
default void onApplicationEvent() { /*do nothing*/ }
}

How to run Scheduled task after specific CommandLineRunner?

I do some initializations in a CommandLineRunner's run function and I want my Scheduled task to begin schedule after the initialization, how can I achive this?
For example, I have the CommandLineRunner:
#Component
public class MyCommandLineRunner implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
// Initializations
// ...
}
}
And the task scheduler:
public class SchedClass {
#Scheduled(fixedRate = ONE_SECOND)
public void sched() {
}
}
What can I do to make sched() runs after run() runs?
class MySchedulingConfigurer implements SchedulingConfigurer {
private ScheduledTaskRegistrar taskRegistrar;
private IntervalTask task;
public MySchedulingConfigurer(IntervalTask task) {
this.task = task;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
this.taskRegistrar = taskRegistrar;
}
public void resume() {
this.taskRegistrar.scheduleFixedRateTask(task);
}
}
#Configuration
class SchedulerConfiguration {
#Autowired
private MySchedulingConfigurer schedulingConfigurer;
#Bean
public MySchedulingConfigurer mySchedulingConfigurer() {
IntervalTask task = new IntervalTask(new MyTask(), 5000L);
return new MySchedulingConfigurer(task);
}
public static class MyTask implements Runnable {
#Override
public void run() {
System.out.println("===> task executed...");
}
}
#EventListener
public void startScheduler(ApplicationReadyEvent event){
schedulingConfigurer.resume();
}
}
#Component
class CacheLoadingRunner implements CommandLineRunner {
#Autowired
private MySchedulingConfigurer schedulingConfigurer;
#Override
public void run(String... args) throws Exception {
schedulingConfigurer.resume();
}
}
1) Add #EnableScheduling
#EnableScheduling
#SpringBootApplication
public class MyCommandLineRunner implements CommandLineRunner {
...
}
2) It makes no sense to denote Spring Boot application with #Component
UPDATED
It could be achieved manually. Provide a TaskScheduler
#Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(5);
threadPoolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
return threadPoolTaskScheduler;
}
Inject it into MyCommandLineRunner along with SchedClass and run
#Component
public class MyCommandLineRunner implements CommandLineRunner {
#Autowired
private ThreadPoolTaskScheduler taskScheduler;
#Autowired
private SchedClass schedBean;
#Override
public void run(String... args) throws Exception {
// ...
taskScheduler.scheduleWithFixedDelay(() -> schedBean.sched(), ONE_SECOND);
// ...
}
}

Stop a scheduling job from rest endpoint

I am doing a Spring Boot Project
This is the main class
#SpringBootApplication
#ComponentScan(basePackages="blabla.quartz")
#EnableScheduling
public class App
{
public static void main( String[] args )
{
ConfigurableApplicationContext context =SpringApplication.run(App.class, args);
}
}
This is the controller
#RestController
public class Controller {
#Autowired
private SampleTask m_sampletask;
#Autowired TaskScheduler taskScheduler;
ScheduledFuture scheduledFuture;
int jobid=0;
#RequestMapping(value = "start/{job}", method = RequestMethod.GET)
public void start(#PathVariable String job) throws Exception {
m_sampletask.addJob(job);
Trigger trigger = new Trigger(){
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
org.quartz.CronExpression cronExp=null;
CronSequenceGenerator generator = new CronSequenceGenerator("0 * * ? * *");
Date nextExecutionDate = generator.next(new Date());
System.out.println(nextExecutionDate);
return nextExecutionDate;
}
};
scheduledFuture = taskScheduler.schedule(m_sampletask, trigger);
}
}
This is the ScheduleConfigurer implementation
#Service
public class MyTask implements SchedulingConfigurer{
#Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("somegroup-");
scheduler.setPoolSize(10);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(20);
return scheduler;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
}
}
This is the class which I am calling from controller as scheduled job
#Component
public class SampleTask implements Runnable{
private List<String> jobs=new ArrayList<String>();
private String jobName;
public void addJob(String job){
jobName=job;
}
#Override
public void run() {
System.out.println("Currently running "+jobName);
}
}
How to stop the schedule job by a rest endpoint(Suppose "/stop/{jobname}").. When I have started the job using the "/start/{jobname}" rest endpoint?
You will probably need to use the quartz scheduler (if not already), and add a service with the required methods, then inject that service into your controller.
There's a decent example here: https://github.com/javabypatel/spring-boot-quartz-demo
If you want an in-memory job store (that isn't a database), checkout the RAMJobStore: http://www.quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigRAMJobStore.html
Stop Example
This is an excerpt from the demo project. Credit goes to Jayesh Patel: https://github.com/javabypatel
/**
* Stop a job
*/
#Override
public boolean stopJob(String jobName) {
System.out.println("JobServiceImpl.stopJob()");
try{
String jobKey = jobName;
String groupKey = "SampleGroup";
Scheduler scheduler = schedulerFactoryBean.getScheduler();
JobKey jkey = new JobKey(jobKey, groupKey);
return scheduler.interrupt(jkey);
} catch (SchedulerException e) {
System.out.println("SchedulerException while stopping job. error message :"+e.getMessage());
e.printStackTrace();
}
return false;
}

Spring update scheduler

I have a scheduled job in Spring, I get its cron from my database.
Every time it is executed, the next execution time is updated. So, if it is configured to run every 10 minutes, I can change the value into the database to schedule that job every 15 minutes.
The problem is that I have to wait for the execution to get the updated cron: if a job is scheduled every 15 minutes and I want to change this value to be every 2 minutes, I have to wait for the next execution (up to 15 minutes) to have this job every 2 minutes.
Is there a way to get this job rescheduled after I update the database?
I thought to destroy and refresh this bean, but it is not working (maybe it is not possible or something was wrong in my implementation). Maybe there is a way to trigger an event to execute method configureTask.
Here the snippet of my scheduled job.
#EnableScheduling
#Component
public class MyClass implements SchedulingConfigurer {
private static final String JOB = "My personal task";
#Autowired
JobRepository jobRepository;
#Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.addTriggerTask(new Runnable() {
#Override
public void run() {
System.out.println("Hello World!");
}
}, new Trigger() {
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
JobScheduled byJobNameIgnoreCase = jobRepository.findByJobNameIgnoreCase(JOB); // read from database
String cron = byJobNameIgnoreCase.getCrontab();
CronTrigger trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(triggerContext);
}
});
}
}
To manage this, I created a SchedulerOrchestrator, which manages my jobs. The jobs contain a SchedulerFuture.
Here the code that I hope can help someone else.
Let's start with an interface which will be implemented by my jobs:
public interface SchedulerObjectInterface {
void start();
void stop();
}
Every job needs a ScheduledFuture to stop and needs to autowire a TaskScheduler to be scheduled. Here a sample of one job (you can create as many as you want):
#Component
public class MyFirstJob implements SchedulerObjectInterface {
private static final Logger log = LoggerFactory.getLogger(MyFirstJob.class);
public static final String JOB = "MyFirstJob";
#Autowired
JobRepository jobRepository;
private ScheduledFuture future;
#Autowired
private TaskScheduler scheduler;
#Override
public void start() {
future = scheduler.schedule(new Runnable() {
#Override
public void run() {
System.out.println(JOB + " Hello World! " + new Date());
}
}, new Trigger() {
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
String cron = cronConfig();
System.out.println(cron);
CronTrigger trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(triggerContext);
}
});
}
#Override
public void stop() {
future.cancel(false);
}
// retrieve cron from database
private String cronConfig() {
JobScheduled byJobNameIgnoreCase = jobRepository.findByJobNameIgnoreCase(JOB);
return byJobNameIgnoreCase.getCrontab();
}
}
Finally we can add our jobs to an orchestrator:
#Configuration
public class SchedulerOrchestrator {
private static final Logger log = LoggerFactory.getLogger(SchedulerOrchestrator.class);
private static Map<String, SchedulerObjectInterface> schduledJobsMap = new HashMap<>();
#Autowired
JobRepository jobRepository;
#Autowired
MyFirstJob myFirstJob;
#Autowired
MySecondJob mySecondJob;
#Autowired
TaskScheduler scheduler;
#PostConstruct
public void initScheduler() {
schduledJobsMap.put(MyFirstJob.JOB, myFirstJob);
schduledJobsMap.put(MySecondJob.JOB, mySecondJob);
startAll();
}
public void restart(String job) {
stop(job);
start(job);
}
public void stop(String job) {
schduledJobsMap.get(job).stop();
}
public void start(String job) {
schduledJobsMap.get(job).start();
}
public void startAll() {
for (SchedulerObjectInterface schedulerObjectInterface : schduledJobsMap.values()) {
schedulerObjectInterface.start();
}
}
#Bean
public TaskScheduler scheduler() {
return new ThreadPoolTaskScheduler();
}
}
Consider this approach. Instead of adding and deletion scheduled tasks, you may check every minute (or with another precision) actual moment against your views and run necessary tasks immediately. This will be easier. Check Quartz Scheduler, its CronExpression has isSatisfiedBy(Date date) method.
#Scheduled(cron = "5 * * * * *) // do not set seconds to zero, cause it may fit xx:yy:59
public void runTasks() {
LocalTime now = LocalTime.now(); // or Date now = new Date();
// check and run
}

Resources