All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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