io.jooby.quartz.QuartzModule Maven / Gradle / Ivy
/**
* Jooby https://jooby.io
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
* Copyright 2014 Edgar Espina
*/
package io.jooby.quartz;
import com.typesafe.config.Config;
import io.jooby.Extension;
import io.jooby.Jooby;
import io.jooby.ServiceKey;
import io.jooby.ServiceRegistry;
import io.jooby.SneakyThrows;
import io.jooby.internal.quartz.ConnectionProviderImpl;
import io.jooby.internal.quartz.JobFactoryImpl;
import io.jooby.internal.quartz.JobGenerator;
import io.jooby.internal.quartz.JobMethodDetail;
import io.jooby.internal.quartz.JobRegistry;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.jdbcjobstore.JobStoreTX;
import org.quartz.simpl.PropertySettingJobFactory;
import org.quartz.utils.DBConnectionManager;
import javax.annotation.Nonnull;
import javax.sql.DataSource;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Stream;
import static org.quartz.impl.StdSchedulerFactory.PROP_SCHED_INSTANCE_ID;
/**
* Scheduler module using Quartz: http://www.quartz-scheduler.org.
*
* {@code
* {
* install(new QuartzModule(SampleJob.class));
*
* }
*
* public class SampleJob implements Job {
*
* #64;Scheduled("10m")
* public void execute(JobExecutionContext context) {
*
* }
* }
* }
*
* Implementation of {@link org.quartz.Job} is optional:
*
* {@code
* public class MyJobs {
*
* #64;Scheduled("1m")
* public void everyMinute() {
* ....
* }
*
* #64;Scheduled("1h")
* * public void everyHour() {
* * ....
* * }
* }
* }
*
* Cron expression are supported too. Check the {@link Scheduled} annotation for possible schedule
* expressions.
*
* Job key are generated from container class and method name. Example: SampleJob.execute,
* MyJob.everyMinute, MyJob.everyHour.
*
* Jobs can be enabled/disabled (paused) at start up time by setting the enabled
* property for each job key:
*
*
* org.quartz.jobs.SampleJob.execute.enabled = false
*
*
* Now the SampleJob.execute
is going to be paused at startup time.
*
* The {@link QuartzApp} added a REST API to trigger, interrupt, pause, resume jobs.
*
* @author edgar
* @since 2.5.1
*/
public class QuartzModule implements Extension {
private List> jobs;
private Scheduler scheduler;
/**
* Creates Quartz module and register the given jobs.
*
* @param jobs Job classes.
*/
public QuartzModule(final Class>... jobs) {
this.jobs = Arrays.asList(jobs);
}
/**
* Creates Quartz module and register the given jobs.
*
* @param jobs Job classes.
*/
public QuartzModule(final List> jobs) {
this.jobs = jobs;
}
/**
* Creates Quartz module and register the given jobs. Uses an user provided schedule, schedule
* is started at application start up time and shutdown on application shutdown.
*
* @param scheduler Provided scheduler.
* @param jobs Job classes.
*/
public QuartzModule(@Nonnull Scheduler scheduler, final Class>... jobs) {
this.scheduler = scheduler;
this.jobs = Arrays.asList(jobs);
}
/**
* Creates Quartz module and register the given jobs. Uses an user provided schedule, schedule
* is started at application start up time and shutdown on application shutdown.
*
* @param scheduler Provided scheduler.
* @param jobs Job classes.
*/
public QuartzModule(@Nonnull Scheduler scheduler, final List> jobs) {
this.scheduler = scheduler;
this.jobs = jobs;
}
@Override public void install(@Nonnull Jooby application) throws Exception {
Config config = application.getConfig();
Map jobMap = JobGenerator.build(application, jobs);
Properties properties = properties(config);
Scheduler scheduler = this.scheduler == null
? newScheduler(application)
: this.scheduler;
jobMap.keySet().stream()
.filter(JobMethodDetail.class::isInstance)
.map(JobMethodDetail.class::cast)
.forEach(detail -> {
/** We need a registry in case of non-ram store: */
JobRegistry.put(detail.getKey(), application, detail.getJobMethod());
});
ServiceRegistry services = application.getServices();
services.putIfAbsent(Scheduler.class, scheduler);
String schedulerName = scheduler.getSchedulerName();
if (services.put(ServiceKey.key(Scheduler.class, schedulerName), scheduler) != null) {
throw new IllegalStateException("Scheduler already exists: " + schedulerName);
}
application.onStarted(() -> {
for (Map.Entry e : jobMap.entrySet()) {
JobDetail jobDetail = e.getKey();
Trigger trigger = e.getValue();
boolean jobEnabled = isJobPaused(properties, jobDetail.getKey());
if (scheduler.checkExists(jobDetail.getKey())) {
// make sure trigger is updated
scheduler.rescheduleJob(trigger.getKey(), trigger);
} else {
scheduler.scheduleJob(jobDetail, trigger);
}
if (jobEnabled) {
application.getLog()
.info("{} {}", jobDetail.getKey(), trigger.getDescription());
} else {
scheduler.pauseJob(jobDetail.getKey());
application.getLog()
.info("{} {} (PAUSED)", jobDetail.getKey(), trigger.getDescription());
}
}
if (!scheduler.isStarted()) {
scheduler.start();
}
});
boolean waitForJobsToComplete = Boolean
.parseBoolean(properties.getProperty("org.quartz.scheduler.waitForJobsToComplete"));
application.onStop(() -> scheduler.shutdown(waitForJobsToComplete));
}
/**
* Creates a new scheduler.
*
* @param application Application.
* @return New scheduler.
*/
public static @Nonnull Scheduler newScheduler(@Nonnull Jooby application) {
try {
Scheduler scheduler = newScheduleFactory(application).getScheduler();
scheduler.setJobFactory(new JobFactoryImpl(application, new PropertySettingJobFactory()));
return scheduler;
} catch (SchedulerException e) {
throw SneakyThrows.propagate(e);
}
}
/**
* Creates a new scheduler factory.
*
* @param application Application.
* @return New scheduler factory.
*/
public static @Nonnull StdSchedulerFactory newScheduleFactory(@Nonnull Jooby application) {
try {
Properties properties = properties(application.getConfig());
if (JobStoreTX.class.getName().equals(properties.getProperty("org.quartz.jobStore.class"))) {
configureJdbcStore(application, properties);
}
return new StdSchedulerFactory(properties);
} catch (SchedulerException e) {
throw SneakyThrows.propagate(e);
}
}
private static boolean isJobPaused(Properties properties, JobKey key) {
return Stream.of(
"org.quartz.jobs." + key.toString() + ".enabled",
"org.quartz.jobs." + key.getGroup() + ".enabled"
).map(k -> properties.getProperty(k))
.filter(Objects::nonNull)
.findFirst()
.map(v -> v.equals("true"))
.orElse(true);
}
private static Properties properties(final Config config) {
Properties props = new Properties();
props.setProperty("org.quartz.scheduler.waitForJobsToComplete", "false");
hostName().ifPresent(hostname -> props.setProperty(PROP_SCHED_INSTANCE_ID, hostname));
props.setProperty("org.quartz.scheduler.skipUpdateCheck", "true");
props.setProperty("org.quartz.threadPool.threadNamePrefix", "scheduler");
props.setProperty("org.quartz.threadPool.threadCount",
Integer.toString(Runtime.getRuntime().availableProcessors()));
if (config.hasPath("org.quartz")) {
// dump
config.getConfig("org.quartz").entrySet().forEach(
e -> props.setProperty("org.quartz." + e.getKey(), e.getValue().unwrapped().toString()));
}
return props;
}
private static Optional hostName() {
try {
return Optional.ofNullable(InetAddress.getLocalHost().getHostName());
} catch (UnknownHostException e) {
return Optional.empty();
}
}
private static void configureJdbcStore(Jooby application, Properties properties) {
String dataSourceName = properties.getProperty("org.quartz.jobStore.dataSource");
ServiceRegistry registry = application.getServices();
DataSource dataSource = Optional.ofNullable(dataSourceName)
.map(key -> registry.getOrNull(ServiceKey.key(DataSource.class, key)))
.orElseGet(() -> registry.getOrNull(DataSource.class));
if (dataSource == null) {
// TODO: replace with usage exception
throw new IllegalArgumentException("DataSource not found: " + dataSourceName);
}
String dataSourceKey = Optional.ofNullable(dataSourceName).orElse("db");
properties.setProperty("org.quartz.jobStore.dataSource", dataSourceKey);
DBConnectionManager
.getInstance().addConnectionProvider(dataSourceKey, new ConnectionProviderImpl(dataSource));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy