I am using Spring Boot 2.6.6 and I want to configure scheduled tasks at runtime. The task configs are saved in a database. Here is my code:
#Configuration
#EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(SchedulerConfig.class);
private BackupJobsService backupJobsService;
private BackupService backupService;
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
public SchedulerConfig(#Autowired BackupJobsService backupJobsService, #Autowired BackupService backupService) {
this.backupJobsService = backupJobsService;
this.backupService = backupService;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
logger.info("Register all custom cron jobs...");
logger.debug("====> SchedulerConfig: configureTasks <====");
List<BackupJob> listOfJobs = backupJobsService.findAll();
for (BackupJob backupJob : listOfJobs) {
BackupTask backupTask = new BackupTask(backupJob, backupService);
Trigger trigger = new Trigger() {
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
logger.debug("====> SchedulerConfig: nextExecutionTime (" + backupJob.getJobName() + ") <====");
CronTrigger crontrigger = new CronTrigger(backupJob.getJobConfig());
return crontrigger.nextExecutionTime(triggerContext);
}
};
taskRegistrar.addTriggerTask(backupTask, trigger);
}
}
#Override
public void destroy() throws Exception {
logger.debug("====> SchedulerConfig: destroy <====");
if (executor != null) {
executor.shutdownNow();
}
}
}
The object backupJob has all Infos about the job. In the field JobConfig save the cron string for the job.
This class work perfect if I start the spring boot application. How can I reconfigure the tasks without a restart of the spring boot application ?
Related
I have a spring batch process which does data reading from the database and writing to file. Basically, the scenario is, that the user can send a request and the job will start and execute the process. But the issue is if the user sends the request 5 times there will be 5 different spring jobs started and running. But those are duplicates. So is there a way that we can avoid or block creating duplicate spring jobs?
You can create a JobExecutionListener that stops the current job execution if another one is already running and configure your job with that listener...
public class SingleExecutionJobListener implements JobExecutionListener {
private static String MATCH_ALL_PATTERN = ".*";
#Autowired
private JobExplorer jobExplorer;
#Autowired
private JobRegistry jobRegistry;
private String jobNamePattern = MATCH_ALL_PATTERN;
#Override
public void beforeJob(JobExecution jobExecution) {
Collection<String> jobNames = jobRegistry.getJobNames();
for (String jobName : jobNames) {
if (jobName.matches(StringUtils.defaultIfBlank(jobNamePattern, MATCH_ALL_PATTERN))) {
Set<JobExecution> jobExecutions = jobExplorer.findRunningJobExecutions(jobName);
if (CollectionUtils.isNotEmpty(jobExecutions)) {
for (JobExecution execution : jobExecutions) {
if (execution.getJobInstance().getId().compareTo(jobExecution.getJobInstance().getId()) != 0) {
jobExecution.stop();
throw new IllegalStateException(jobName + " instance " + execution.getJobInstance().getId()
+ " is currently running. Please restart this job when " + jobName + " has finished.");
}
}
}
}
}
}
#Override
public void afterJob(JobExecution jobExecution) {}
public String getJobNamePattern() {
return jobNamePattern;
}
public void setJobNamePattern(String jobNamePattern) {
this.jobNamePattern = jobNamePattern;
}
}
I have the class below in which I want to configure my Camunda process engine.
I want to make the engine deployment-aware and set the backoff-time-in-millis.
I can do the former using config.setJobExecutorDeploymentAware(true);.
How can I set the backoff-time-in-millis and other parameters of the job executor?
The obvious solution (config.getJobExecutor().setBackoffTimeInMillis(100);) is not applicable because when the method org.example.Config#processEngineConfiguration is called, config.getJobExecutor() is equal to null.
#Configuration
#Import( SpringProcessEngineServicesConfiguration.class )
public class Config {
private static final Logger LOGGER = LoggerFactory.getLogger(Config.class);
#Autowired
#Qualifier("camundaBpmDataSource")
private DataSource dataSource;
#Autowired
#Qualifier("camundaTxManager")
private PlatformTransactionManager txManager;
#Autowired
private ResourcePatternResolver resourceLoader;
#Bean
public SpringProcessEngineConfiguration processEngineConfiguration() {
final SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
config.setJobExecutorDeploymentAware(true);
config.setIdGenerator(new StrongUuidGenerator());
config.setDataSource(dataSource);
config.setTransactionManager(txManager);
config.setDatabaseSchemaUpdate("true");
config.getProcessEnginePlugins().add(new SpinProcessEnginePlugin());
config.setHistory(HistoryLevel.HISTORY_LEVEL_FULL.getName());
config.setJobExecutorActivate(true);
config.setMetricsEnabled(false);
final Logger logger = LoggerFactory.getLogger("History Event Handler");
final HistoryEventHandler testHistoryEventHandler = new HistoryEventHandler() {
#Override
public void handleEvent(final HistoryEvent evt) {
LOGGER.debug("handleEvent | " + evt.getProcessInstanceId() + " | "
+ evt.toString());
}
#Override
public void handleEvents(final List<HistoryEvent> events) {
for (final HistoryEvent curEvent : events) {
handleEvent(curEvent);
}
}
};
config.setHistoryEventHandler(new CompositeHistoryEventHandler(Collections.singletonList(testHistoryEventHandler)));
try {
final Resource[] bpmnResources = resourceLoader.getResources("classpath:*.bpmn");
final Resource[] dmnResources = resourceLoader.getResources("classpath:*.dmn");
config.setDeploymentResources(addAll(bpmnResources, dmnResources));
} catch (final IOException exception) {
exception.printStackTrace();
LOGGER.error("An error occurred while trying to deploy BPMN and DMN files", exception);
}
return config;
}
}
Add following code to the method processEngineConfiguration() above:
final ThreadPoolJobExecutor executor = new DefaultJobExecutor();
executor.setMaxJobsPerAcquisition(3);
executor.setWaitTimeInMillis(500);
executor.setLockTimeInMillis(300000);
executor.setMaxBackoff(150);
executor.setMaxWait(50);
final SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
config.setJobExecutor(executor);
config.setJobExecutorDeploymentAware(true);
I'm dwelling with Quartz from days..
I need to create, the app starts, some triggers and job details..
So, this is my Job
#DisallowConcurrentExecution
public class TimeoutJob extends QuartzJobBean{
public final String ID = "idInterruttore";
private final Logger logger = Logger.getLogger(TimeoutJob.class);
#Autowired InterruttoreService interruttoreService;
#Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
int idInterruttore = dataMap.getIntFromString(ID);
Interruttore interruttore = interruttoreService.findById(idInterruttore);
logger.debug("Job reached for " + interruttore.getNomeInterruttore());
}
}
Then i configure some bean in QuartzConfiguration.java
#Configuration
#ComponentScan("it.besmart")
public class QuartzConfiguration {
#Autowired
ApplicationContext applicationContext;
#Bean
public SchedulerFactoryBean scheduler() {
SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
schedulerFactory.setJobFactory(springBeanJobFactory());
return schedulerFactory;
}
#Bean
public SpringBeanJobFactory springBeanJobFactory() {
AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
}
Now, I have a JobManager.class which manage jobDetails and Triggers
#Service("jobManager")
public class JobManager {
private final Logger logger = Logger.getLogger(JobManager.class);
#Autowired
SchedulerFactoryBean scheduler;
#Autowired
InterruttoreService interruttoreService;
#PostConstruct
public void createInitialJobs() {
logger.debug("Start ut jobs to create");
List<Interruttore> interruttori = interruttoreService.findAllSwitches();
Date now = new Date();
for (int i = 0; i < interruttori.size(); i++) {
Interruttore interruttore = interruttori.get(i);
if (interruttore.getTimeoutDate().after(now) && interruttore.isStato()) {
// JobDetail and Trigger creation
createJob(interruttore, interruttore.getTimeoutDate());
}
}
}
public void createJob(Interruttore interruttore, Date richiesta) {
JobDetailFactoryBean jobDetail = new JobDetailFactoryBean();
jobDetail.setJobClass(TimeoutJob.class);
jobDetail.setName("Job detail for " + interruttore.getNomeInterruttore());
jobDetail.setDescription("Job Description");
jobDetail.setDurability(true);
Map<String, Integer> map = new HashMap<String,Integer>();
map.put("idInterruttore", interruttore.getIdInterruttore());
jobDetail.setJobDataAsMap(map);
long future = richiesta.getTime() - new Date().getTime();
logger.debug("next timeout is " + future / 1000 / 60 + " minuti for " + interruttore.getNomeInterruttore());
//trigger creation
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
trigger.setName("myTrigger"+interruttore.getNomeInterruttore());
trigger.setGroup("timeoutTriggers");
trigger.setJobDetail(jobDetail.getObject());
trigger.setStartDelay(0);
trigger.setRepeatCount(1);
trigger.setRepeatInterval(future);
trigger.afterPropertiesSet();
logger.debug("Trigger for " + interruttore.getNomeInterruttore());
logger.debug("Trigger object is :" + trigger.getObject());
logger.debug("Next Trigger date " + trigger.getObject().getFinalFireTime());
try {
scheduler.getScheduler().scheduleJob(jobDetail.getObject(), trigger.getObject());
} catch (SchedulerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
When launching the app, the #PostConstruct method tries to create the triggers, but i'm getting an exception when creating jobManager
Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobManager': Invocation of init method failed; nested exception is java.lang.NullPointerException
caused by
Caused by: java.lang.NullPointerException
at org.springframework.scheduling.quartz.SimpleTriggerFactoryBean.afterPropertiesSet(SimpleTriggerFactoryBean.java:231)
at it.besmart.quartz.JobManager.createJob(JobManager.java:85)
at it.besmart.quartz.JobManager.createInitialJobs(JobManager.java:54)
which is
trigger.afterPropertiesSet();
as my triggers are not created...
There is a bug in the spring-context-support jar 4.2.5 version.
sti.setJobKey(this.jobDetail.getKey());
i.e. jobDetail can be null.
In the new versions it is fixed. I checked 4.3.2 version.
You can use 4.3.2 or later.
In 4.3.2 version
if (this.jobDetail != null) {
sti.setJobKey(this.jobDetail.getKey());
}
I like to run two tasks undependent from each other. The problem is, that one of the task has a blocking part. As a result the second task is waiting for the first task. Because of that, I think both tasks are running on the same thread. But I like to run each task on a different thread with the aim, that each task don't have to wait for each other.
I have a configuration class AppConfig:
#Configuration
#EnableAsync
#EnableScheduling
public class AppConfig
{...
There I have defined two beans:
#Bean(initMethod = "watchFtp")
public FTPWatchScheduledTask ftpWatchScheduledTaskService()
{
FTPWatchScheduledTask ftpWatchScheduledTask = new FTPWatchScheduledTaskService();
return ftpWatchScheduledTask;
}
and
#Bean(initMethod = "watch")
public FileWatch fileWatchService()
{
...
return fileWatchService;
}
Further the AppConfig is registered by the context:
public class FileSyncApplicationInitializer
implements WebApplicationInitializer
{
private static final Log log = LogFactory.getLog(FileSyncApplicationInitializer.class);
private static AnnotationConfigWebApplicationContext context;
#Override
public void onStartup(ServletContext container)
{
// Create the Spring application context
log.debug("Initializing file synchronisation ...");
context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
log.debug("Contexts initialized ...");
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
Both defined init-Methods of each bean are annotated with "#Async":
#Async
public void watch() throws IOException
{
WatchService watcher = FileSystems.getDefault().newWatchService();
WatchKey key = temporaryDir.register(watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
for (; ; )
{
try
{
key = watcher.take(); // take is waiting
}
catch (InterruptedException e)
{
e.printStackTrace();
}
for (WatchEvent<?> pollEvent : key.pollEvents())
{
WatchEvent.Kind<?> kind = pollEvent.kind();
if (kind == StandardWatchEventKinds.ENTRY_CREATE)
{
WatchEvent<Path> event = (WatchEvent<Path>)pollEvent;
Path fileName = (Path)key.watchable();
Path fullPath = fileName.resolve(event.context());
orderImportService.persistOrderXml(fileName);
log.info("something was persisted");
}
}
boolean valid = key.reset();
if (!valid)
{
break;
}
}
}
Second init-method:
#Async
#Scheduled(fixedRate = 5000)
public void watchFtp()
{
log.info(String.format("test time: " + dateFormat.format(new Date())));
}
In our REST-Service we want to implement a job that checks something every 10 seconds. So we thought we could use Quartz to make a Job that cover this. But the problem is, that we need to inject a singleton, because it is used in the job and the job seems to be not in the context of our service, so the injected class is always null (NullPointerException).
So is there another possible solution to achieve such a job without using Quartz? Already tried to write our own JobFactory that connects the job with the BeanManager, but it didnt work at all.
This is the code for the job that is not working:
#Stateless
public class GCEStatusJob implements Job, Serializable{
private Logger log = LoggerFactory.getLogger(GCEStatusJob.class);
#Inject
SharedMemory sharedMemory;
#Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
GoogleComputeEngineFactory googleComputeEngineFactory = new GoogleComputeEngineFactory();
List<HeartbeatModel> heartbeatList = new ArrayList<>(sharedMemory.getAllHeartbeats());
List<GCE> gceList = googleComputeEngineFactory.listGCEs();
List<String> ipAddressList = gceList.stream().map(GCE::getIp).collect(Collectors.toList());
for(HeartbeatModel heartbeat : heartbeatList){
if(ipAddressList.contains(heartbeat.getIpAddress())){
long systemTime = System.currentTimeMillis();
if(systemTime-heartbeat.getSystemTime()>10000){
log.info("Compute Engine mit IP "+heartbeat.getIpAddress()+" antwortet nicht mehr. Wird neu gestartet!");
String name = gceList.stream().filter((i) -> i.getIp().equals(heartbeat.getIpAddress())).findFirst().get().getName();
googleComputeEngineFactory.resetGCE(name);
}
}
}
}
}
SharedMemory is always null.
I have used Scheduler context map to achive this. You can try this.
In REST API when we create a Scheduler we can use the Context map to pass the parameters to Job
#Path("job")
public class RESTApi {
private String _userID;
public String get_userID() {
return _userID;
}
public void set_userID(String _userID) {
this._userID = _userID;
}
#GET
#Path("/start/{userId}")
public void startJob(#PathParam("userId") String userID) {
_userID = userID;
try {
SimpleTrigger trigger = new SimpleTrigger();
trigger.setName("updateTrigger");
trigger.setStartTime(new Date(System.currentTimeMillis() + 1000));
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
trigger.setRepeatInterval(1000);
JobDetail job = new JobDetail();
job.setName("updateJob");
job.setJobClass(GCEStatusJob.class);
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.getContext().put("apiClass", this);
scheduler.start();
scheduler.scheduleJob(job, trigger);
} catch (Exception e) {
e.printStackTrace();
}
}
}
JOB implementation
public class GCEStatusJob implements Job {
#Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
RESTApi apiClass;
try {
apiClass = ((RESTApi) arg0.getScheduler().getContext().get("apiClass"));
System.out.println("User name is" + apiClass.get_userID());
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
Correct me, if my understanding is wrong.