I have Sprint Boot - Java 8 application that has a quartz job that I configure upon start up and set a schedule. The job runs automatically per the schedule as you would expect with a quartz job. However, now I want to be able to allow the user to manually trigger these jobs with a click of a button on the front end WITHOUT messing up the normal scheduling of that job. Here are all my relevant files.
application.yml
quartz:
fooCron: 0 0 1 * * ?
fooGroup: foo-quartz-group
QuartzConfig.java
@Configuration
@ConfigurationProperties(prefix = "quartz")
public class QuartzConfig {
private String fooCron;
private String fooGroup;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private DataSource dataSource;
@Bean
public SchedulerFactoryBean quartzScheduler() {
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
Trigger[] triggers = {fooTrigger().getObject()};
SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();
quartzScheduler.setJobFactory(jobFactory);
quartzScheduler.setTransactionManager(transactionManager);
quartzScheduler.setDataSource(dataSource);
quartzScheduler.setOverwriteExistingJobs(true);
quartzScheduler.setSchedulerName("foo-scheduler");
quartzScheduler.setQuartzProperties(quartzProperties());
quartzScheduler.setTriggers(triggers);
return quartzScheduler;
}
@Bean
public CronTriggerFactoryBean fooTrigger() {
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setJobDetail(fooJob().getObject());
cronTriggerFactoryBean.setCronExpression(fooCron);
cronTriggerFactoryBean.setGroup(fooGroup);
return cronTriggerFactoryBean;
}
@Bean
public JobDetailFactoryBean fooJob() {
JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
jobDetailFactoryBean.setJobClass(FooJob.class);
jobDetailFactoryBean.setGroup(fooGroup);
jobDetailFactoryBean.setDurability(true);
return jobDetailFactoryBean;
}
@Bean
public Properties quartzProperties() {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz/quartz.properties"));
Properties properties = null;
try {
propertiesFactoryBean.afterPropertiesSet();
properties = propertiesFactoryBean.getObject();
} catch (IOException e) {
}
return properties;
}
//setters
}
FooJob.java
@Service
public class FooJob implements Job {
private final FooRepository fooRepo; //This is a repository class annotated with @Repository.
public FooJob(FooRepository fooRepo) {
this.fooRepo = fooRepo;
}
@Override
public void execute(final JobExecutionContext context) throws JobExecutionException {
//do stuff
}
}
Now this runs just fun on a timed scheduled. Per the cron configuration in the yml file, 0 0 1 * * ?, the job executes everyday at 1am. Great! But now I want to execute this manually. So I build a controller to receive manual trigger requests from the UI.
QuartzController.java
@RestController
@RequestMapping("/quartz")
public class QuartzController {
private SchedulerFactoryBean schedulerFactoryBean;
private Scheduler scheduler;
public DevopsController(final SchedulerFactoryBean quartzScheduler) {
this.schedulerFactoryBean = quartzScheduler;
scheduler = schedulerFactoryBean.getScheduler();
}
@PostMapping("/execute")
public ResponseEntity executeJob() {
HttpStatus status = OK;
try {
TriggerKey triggerKey = new TriggerKey("fooTrigger", "foo-quartz-group");
Trigger trigger = scheduler.getTrigger(triggerKey);
ScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).withRepeatCount(0);
JobDetail jobDetail = scheduler.getJobDetail(trigger.getJobKey());
Trigger newTrigger = TriggerBuilder.newTrigger()
.forJob(jobDetail)
.startNow()
.withIdentity(triggerKey)
.withSchedule(scheduleBuilder)
.startAt(Date.from(LocalDate.now().atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()))
.build();
//I have tried all 3 of the following lines
scheduler.scheduleJob(jobDetail, new HashSet<>(Arrays.asList(trigger)), true);
//scheduler.addJob(jobDetail, true);
//scheduler.rescheduleJob(triggerKey, newTrigger);
} catch (SchedulerException e) {
status = BAD_REQUEST;
}
return new ResponseEntity<>(status);
}
}
But everytime I run the application and hit the controller's scheduleJob method I get the following error in the console:
org.quartz.SchedulerException: Job instantiation failed
at org.springframework.scheduling.quartz.AdaptableJobFactory.newJob(AdaptableJobFactory.java:45)
at org.quartz.core.JobRunShell.initialize(JobRunShell.java:127)
at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:375)
Caused by: java.lang.InstantiationException: com.test.jobs.FooJob
at java.lang.Class.newInstance(Class.java:427)
at org.springframework.scheduling.quartz.AdaptableJobFactory.createJobInstance(AdaptableJobFactory.java:58)
at org.springframework.scheduling.quartz.SpringBeanJobFactory.createJobInstance(SpringBeanJobFactory.java:74)
at com.test.config.AutowiringSpringBeanJobFactory.createJobInstance(AutowiringSpringBeanJobFactory.java:27)
at org.springframework.scheduling.quartz.AdaptableJobFactory.newJob(AdaptableJobFactory.java:41)
... 2 common frames omitted
Caused by: java.lang.NoSuchMethodException: com.test.jobs.FooJob.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 6 common frames omitted
What exactly am I doing wrong? How do I get this job to run automatically as per schedule but also execute upon a manual request?
I am using sprint boot 1.5.9.RELEASE and quartz 2.2.1
As @scary-wombat said, you have to add a no parameter constructor in FooJob. The problem with your approach is you won't get a FooRepository in that way.
public FooJob() {
}
You have 2 options
1) If FooRepository has a @Respository annotation, you can add a @Autowired annotation to your constructor.
@Autowired
public FooJob(FooRepository fooRepo) {
this.fooRepo = fooRepo;
}
Thanks to you have a @Repository annotation Spring will notice that it can create an instance of the required object to create an instance of the @service.
2) You can add a configuration class.
@Configuration
public class Config {}
And you have to create there an instance of your service (FooJob).
The first option looks better in my humble opinion.
Let me know if it works!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With