Quartz
Quartz is (according to their website) a richly featured, open source job scheduling library that can be integrated within virtually any Java application.
We can configure Quartz to run Job
s using headless access, for numerous use cases, most commonly involving the polling of work to be performed in the background.
For example:
-
period archiving of data to another system, eg blob images to S3
-
aggregating data
-
proactively monitoring health/status
In simple use cases, Spring Boot’s integration with Quartz can be used without bringing in a dependency on this extension.
This extension supports the use case where there is a requirement to pass state from one invocation of a Job
to the next.
Simple use case
The simple use case (that doesn’t require a dependeny on this extension) is demonstrated in the SimpleApp starter app.
-
add the dependency (eg to the webapp module):
pom.xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> (1) <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency>
1 to avoid Slf4j ←→ log4j2 cyclic dependency -
implement quartz’s
Job
interface:SampleJob.java@Component @RequiredArgsConstructor(onConstructor_ = {@Inject}) @Slf4j public class SampleJob implements Job { private final InteractionService interactionService; (1) private final TransactionalProcessor transactionalProcessor; (1) @Override public void execute(JobExecutionContext context) throws JobExecutionException { // ... } }
1 for headless access to the domain object model. -
Set up beans to act as the trigger factory and a job factory:
QuartzModule.java@Configuration @ComponentScan public class QuartzModule { private static final int REPEAT_INTERVAL_SECS = 60; private static final int START_DELAY_SECS = 20; private static final int MILLIS_PER_SEC = 1000; @Bean public JobDetailFactoryBean jobDetail() { val jobDetailFactory = new JobDetailFactoryBean(); jobDetailFactory.setJobClass(SampleJob.class); jobDetailFactory.setDescription("Invoke Sample Job service..."); jobDetailFactory.setDurability(true); return jobDetailFactory; } @Bean public SimpleTriggerFactoryBean trigger(JobDetail job) { val trigger = new SimpleTriggerFactoryBean(); trigger.setJobDetail(job); trigger.setStartDelay(START_DELAY_SECS * MILLIS_PER_SEC); trigger.setRepeatInterval(REPEAT_INTERVAL_SECS * MILLIS_PER_SEC); trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); return trigger; } }
-
include the
QuartzModule
in the application’s top-levelAppManifest
.
More complex use cases
This extension supports a couple of slightly more advanced use cases.
If either are used:
-
update the
pom.xml
dependencies:<dependency> <groupId>org.apache.isis.extensions</groupId> <artifactId>isis-extensions-quartz-impl</artifactId> </dependency>
-
import the extension’s module in your application’s top-level
AppManifest
:@Configuration @Import({ // ... IsisModuleExtQuartzImpl.class, }) // ... public class AppManifest { // ... }
Preserving job state
Sometimes there is a requirement to pass state from one invocation of a job to another. For example, if some external service is unavailable, then we wouldn’t necessarily want to a periodic job to keep trying to connect, creating noise in the logs.
To support this use case, this extension provides the JobExecutionData class, which simplifies the API of Quartz’s job data map.
Injecting domain services into jobs
TODO - it’s possible this boilerplate may be unnecessary? SimpleApp's job seems to be injected into without this extra rigamorole. |
If we want to inject domain services into the Quartz Job
, then we should define a number of additional beans.
These instantiate AutowiringSpringBeanJobFactory as the job factory:
import org.apache.isis.extensions.quartz.spring.AutowiringSpringBeanJobFactory;
@Configuration
@ComponentScan
public class QuartzModule {
// ...
@Bean
public SpringBeanJobFactory springBeanJobFactory() {
val jobFactory = new AutowiringSpringBeanJobFactory(); (1)
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
@Bean
public SchedulerFactoryBean scheduler(
final Trigger trigger,
final JobDetail jobDetail,
final SpringBeanJobFactory sbjf) {
val schedulerFactory = new SchedulerFactoryBean();
schedulerFactory.setJobFactory(sbjf);
schedulerFactory.setJobDetails(jobDetail);
schedulerFactory.setTriggers(trigger);
return schedulerFactory;
}
@Bean
public Scheduler scheduler(
final Trigger trigger,
final JobDetail job,
final SchedulerFactoryBean factory)
throws SchedulerException {
val scheduler = factory.getScheduler();
scheduler.start();
return scheduler;
}
1 | as provided by this extension |