/** * The MIT License * * Copyright (c) 2017, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.easybatch.extensions.quartz; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import java.util.Date; import java.util.logging.Level; import java.util.logging.Logger; import static java.lang.String.format; import static org.easybatch.core.util.Utils.checkNotNull; import static org.quartz.CronScheduleBuilder.cronSchedule; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; /** * Quartz scheduler wrapper used to setup triggers. * * @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) */ public class JobScheduler { private static final Logger LOGGER = Logger.getLogger(JobScheduler.class.getName()); private static final String JOB_NAME_PREFIX = "job-"; private static final String TRIGGER_NAME_PREFIX = "trigger-for-job-"; private static JobScheduler instance; private Scheduler scheduler; JobScheduler() throws JobSchedulerException { org.quartz.spi.JobFactory jobFactory = new JobFactory(); SchedulerFactory schedulerFactory = new StdSchedulerFactory(); try { scheduler = schedulerFactory.getScheduler(); scheduler.setJobFactory(jobFactory); } catch (SchedulerException e) { throw new JobSchedulerException("An exception occurred during scheduler setup", e); } } /** * Get the scheduler instance. * * @return The scheduler instance * @throws JobSchedulerException thrown if an exception occurs while creating the scheduler */ public static JobScheduler getInstance() throws JobSchedulerException { if (instance == null) { instance = new JobScheduler(); } return instance; } /** * Schedule a job to start at a fixed point of time. * * @param job the job to schedule * @param startTime the start time */ public void scheduleAt(final org.easybatch.core.job.Job job, final Date startTime) throws JobSchedulerException { checkNotNull(job, "job"); checkNotNull(startTime, "startTime"); String name = job.getName(); String jobName = JOB_NAME_PREFIX + name; String triggerName = TRIGGER_NAME_PREFIX + name; Trigger trigger = newTrigger() .withIdentity(triggerName) .startAt(startTime) .forJob(jobName) .build(); JobDetail jobDetail = getJobDetail(job, jobName); try { LOGGER.log(Level.INFO, "Scheduling job ''{0}'' to start at {1}", new Object[]{name, startTime}); scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { throw new JobSchedulerException(format("Unable to schedule job '%s'", name), e); } } /** * Schedule a job to start at a fixed point of time. * * @param job the job to schedule * @param startTime the start time * @param strategy to apply when a misfire occurs * @param repeatCount number of times the trigger will repeat */ public void scheduleAt(final org.easybatch.core.job.Job job, final Date startTime, MisfireHandlingStrategy strategy, int repeatCount) throws JobSchedulerException { checkNotNull(job, "job"); checkNotNull(startTime, "startTime"); String name = job.getName(); String jobName = JOB_NAME_PREFIX + name; String triggerName = TRIGGER_NAME_PREFIX + name; SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule(); // Quartz should have provided a strategy interface for this.. if (strategy.equals(MisfireHandlingStrategy.FIRE_NOW)) { simpleScheduleBuilder.withMisfireHandlingInstructionFireNow(); } if (strategy.equals(MisfireHandlingStrategy.IGNORE_MISFIRES)) { simpleScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); } if (strategy.equals(MisfireHandlingStrategy.NEXT_WITH_EXISTING_COUNT)) { simpleScheduleBuilder.withMisfireHandlingInstructionNextWithExistingCount(); } if (strategy.equals(MisfireHandlingStrategy.NEXT_WITH_REMAINING_COUNT)) { simpleScheduleBuilder.withMisfireHandlingInstructionNextWithRemainingCount(); } if (strategy.equals(MisfireHandlingStrategy.NOW_WITH_EXISTING_COUNT)) { simpleScheduleBuilder.withMisfireHandlingInstructionNowWithExistingCount(); } if (strategy.equals(MisfireHandlingStrategy.NOW_WITH_REMAINING_COUNT)) { simpleScheduleBuilder.withMisfireHandlingInstructionNowWithRemainingCount(); } simpleScheduleBuilder.withRepeatCount(repeatCount); Trigger trigger = newTrigger() .withIdentity(triggerName) .startAt(startTime) .forJob(jobName) .withSchedule(simpleScheduleBuilder) .build(); JobDetail jobDetail = getJobDetail(job, jobName); try { LOGGER.log(Level.INFO, "Scheduling job ''{0}'' to start at {1}", new Object[]{name, startTime}); scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { throw new JobSchedulerException(format("Unable to schedule job '%s'", name), e); } } /** * Schedule a job to start at a fixed point of time and repeat with interval period. * * @param job the job to schedule * @param startTime the start time * @param interval the repeat interval in seconds */ public void scheduleAtWithInterval(final org.easybatch.core.job.Job job, final Date startTime, final int interval) throws JobSchedulerException { checkNotNull(job, "job"); checkNotNull(startTime, "startTime"); String name = job.getName(); String jobName = JOB_NAME_PREFIX + name; String triggerName = TRIGGER_NAME_PREFIX + name; SimpleScheduleBuilder scheduleBuilder = simpleSchedule() .withIntervalInSeconds(interval) .repeatForever(); Trigger trigger = newTrigger() .withIdentity(triggerName) .startAt(startTime) .withSchedule(scheduleBuilder) .forJob(jobName) .build(); JobDetail jobDetail = getJobDetail(job, jobName); try { LOGGER.log(Level.INFO, "Scheduling job ''{0}'' to start at {1} and every {2} second(s)", new Object[]{name, startTime, interval}); scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { throw new JobSchedulerException(format("Unable to schedule job '%s'", name), e); } } /** * Schedule a job to start at a fixed point of time and repeat with interval period. * * @param job the job to schedule * @param startTime the start time * @param interval the repeat interval in seconds * @param strategy to apply when a misfire occurs * @param repeatCount number of times the trigger will repeat */ public void scheduleAtWithInterval(final org.easybatch.core.job.Job job, final Date startTime, final int interval, MisfireHandlingStrategy strategy, int repeatCount) throws JobSchedulerException { checkNotNull(job, "job"); checkNotNull(startTime, "startTime"); String name = job.getName(); String jobName = JOB_NAME_PREFIX + name; String triggerName = TRIGGER_NAME_PREFIX + name; SimpleScheduleBuilder scheduleBuilder = simpleSchedule() .withIntervalInSeconds(interval) .withRepeatCount(repeatCount); SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule(); // Quartz should have provided a strategy interface for this.. if (strategy.equals(MisfireHandlingStrategy.FIRE_NOW)) { simpleScheduleBuilder.withMisfireHandlingInstructionFireNow(); } if (strategy.equals(MisfireHandlingStrategy.IGNORE_MISFIRES)) { simpleScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); } if (strategy.equals(MisfireHandlingStrategy.NEXT_WITH_EXISTING_COUNT)) { simpleScheduleBuilder.withMisfireHandlingInstructionNextWithExistingCount(); } if (strategy.equals(MisfireHandlingStrategy.NEXT_WITH_REMAINING_COUNT)) { simpleScheduleBuilder.withMisfireHandlingInstructionNextWithRemainingCount(); } if (strategy.equals(MisfireHandlingStrategy.NOW_WITH_EXISTING_COUNT)) { simpleScheduleBuilder.withMisfireHandlingInstructionNowWithExistingCount(); } if (strategy.equals(MisfireHandlingStrategy.NOW_WITH_REMAINING_COUNT)) { simpleScheduleBuilder.withMisfireHandlingInstructionNowWithRemainingCount(); } simpleScheduleBuilder.withRepeatCount(repeatCount); Trigger trigger = newTrigger() .withIdentity(triggerName) .startAt(startTime) .withSchedule(scheduleBuilder) .forJob(jobName) .build(); JobDetail jobDetail = getJobDetail(job, jobName); try { LOGGER.log(Level.INFO, "Scheduling job ''{0}'' to start at {1} and every {2} second(s)", new Object[]{name, startTime, interval}); scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { throw new JobSchedulerException(format("Unable to schedule job '%s'", name), e); } } /** * Schedule a job with a unix-like cron expression. * * @param job the job to schedule * @param cronExpression the cron expression to use. * For a complete tutorial about cron expressions, please refer to * <a href="http://quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/crontrigger">quartz reference documentation</a>. */ public void scheduleCron(final org.easybatch.core.job.Job job, final String cronExpression) throws JobSchedulerException { checkNotNull(job, "job"); checkNotNull(cronExpression, "cronExpression"); String name = job.getName(); String jobName = JOB_NAME_PREFIX + name; String triggerName = TRIGGER_NAME_PREFIX + name; Trigger trigger = newTrigger() .withIdentity(triggerName) .withSchedule(cronSchedule(cronExpression)) .forJob(jobName) .build(); JobDetail jobDetail = getJobDetail(job, jobName); try { LOGGER.log(Level.INFO, "Scheduling job ''{0}'' with cron expression {1}", new Object[]{name, cronExpression}); scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { throw new JobSchedulerException(format("Unable to schedule job '%s'", name), e); } } /** * Schedule a job with a unix-like cron expression. * * @param job the job to schedule * @param cronExpression the cron expression to use. * For a complete tutorial about cron expressions, please refer to * <a href="http://quartz-scheduler.org/documentation/quartz-2.1.x/tutorials/crontrigger">quartz reference documentation</a>. * @param strategy to apply when a misfire occurs */ public void scheduleCron(final org.easybatch.core.job.Job job, final String cronExpression, MisfireCronHandlingStrategy strategy) throws JobSchedulerException { checkNotNull(job, "job"); checkNotNull(cronExpression, "cronExpression"); String name = job.getName(); String jobName = JOB_NAME_PREFIX + name; String triggerName = TRIGGER_NAME_PREFIX + name; CronScheduleBuilder cronScheduleBuilder = cronSchedule(cronExpression); // Quartz should have provided a strategy interface for this.. if (strategy.equals(MisfireCronHandlingStrategy.DO_NOTHING)) { cronScheduleBuilder.withMisfireHandlingInstructionDoNothing(); } if (strategy.equals(MisfireCronHandlingStrategy.IGNORE_MISFIRES)) { cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); } if (strategy.equals(MisfireCronHandlingStrategy.FIRE_AND_PROCEED)) { cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); } Trigger trigger = newTrigger() .withIdentity(triggerName) .withSchedule(cronScheduleBuilder) .forJob(jobName) .build(); JobDetail jobDetail = getJobDetail(job, jobName); try { LOGGER.log(Level.INFO, "Scheduling job ''{0}'' with cron expression {1}", new Object[]{name, cronExpression}); scheduler.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { throw new JobSchedulerException(format("Unable to schedule job '%s'", name), e); } } /** * Unschedule the given job. * * @param job the job to unschedule * @throws JobSchedulerException thrown if an exception occurs during job unscheduling */ public void unschedule(final org.easybatch.core.job.Job job) throws JobSchedulerException { String jobName = job.getName(); LOGGER.log(Level.INFO, "Unscheduling job ''{0}'' ", jobName); try { scheduler.unscheduleJob(TriggerKey.triggerKey(TRIGGER_NAME_PREFIX + jobName)); } catch (SchedulerException e) { throw new JobSchedulerException(format("Unable to unschedule job '%s'", jobName), e); } } /** * Check if the given job is scheduled. * * @param job the job to check * @return true if the job is scheduled, false else * @throws JobSchedulerException thrown if an exception occurs while checking if the job is scheduled */ public boolean isScheduled(final org.easybatch.core.job.Job job) throws JobSchedulerException { String jobName = job.getName(); try { return scheduler.checkExists(TriggerKey.triggerKey(TRIGGER_NAME_PREFIX + jobName)); } catch (SchedulerException e) { throw new JobSchedulerException(format("Unable to check if the job '%s' is scheduled", jobName), e); } } /** * Start the scheduler. * * @throws JobSchedulerException thrown if the scheduler cannot be started */ public void start() throws JobSchedulerException { try { scheduler.start(); } catch (SchedulerException e) { throw new JobSchedulerException("An exception occurred during scheduler startup", e); } } /** * Stop the scheduler. * <p/> * <strong>Note: The scheduler cannot be re-started and no more jobs can be scheduled.</strong> * * @throws JobSchedulerException thrown if the scheduler cannot be stopped */ public void stop() throws JobSchedulerException { try { scheduler.shutdown(); } catch (SchedulerException e) { throw new JobSchedulerException("An exception occurred during scheduler shutdown", e); } } /** * Check if the scheduler is started. * * @throws JobSchedulerException thrown if the scheduler status cannot be checked */ public boolean isStarted() throws JobSchedulerException { try { return scheduler.isStarted(); } catch (SchedulerException e) { throw new JobSchedulerException("An exception occurred during checking if the scheduler is started", e); } } /** * Check if the scheduler is stopped. * * @throws JobSchedulerException thrown if the scheduler status cannot be checked */ public boolean isStopped() throws JobSchedulerException { try { return scheduler.isShutdown(); } catch (SchedulerException e) { throw new JobSchedulerException("An exception occurred during checking if the scheduler is stopped", e); } } private JobDetail getJobDetail(org.easybatch.core.job.Job job, String jobName) { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("job", job); return newJob(Job.class).withIdentity(jobName).usingJobData(jobDataMap).build(); } }