Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Quartz Triggering Job programmatically

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

like image 696
Richard Avatar asked Jun 25 '26 15:06

Richard


1 Answers

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!

like image 195
Cata Avatar answered Jun 27 '26 05:06

Cata



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!