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

org.opensingular.schedule.quartz.QuartzSingularSchedulerFactory Maven / Gradle / Ivy

There is a newer version: 1.9.7
Show newest version
/*
 * Copyright (C) 2016 Singular Studios (a.k.a Atom Tecnologia) - www.opensingular.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.opensingular.schedule.quartz;

import org.opensingular.lib.commons.base.SingularException;
import org.opensingular.schedule.IScheduledJob;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.RemoteScheduler;
import org.quartz.impl.SchedulerRepository;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.simpl.SimpleThreadPool;
import org.quartz.spi.JobFactory;

import java.io.IOException;
import java.util.Enumeration;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;

/**
 * Factory that creates and configures a Quartz {@link org.quartz.Scheduler}.
 * 

*

Allows registration of JobDetails, Calendars and Triggers, automatically * starting the scheduler on initialization and shutting it down on destruction. * In scenarios that just require static registration of jobs at startup, there * is no need to access the Scheduler instance itself in application code.

*

*

For dynamic registration of jobs at runtime, use a bean reference to * this SchedulerFactoryBean to get direct access to the Quartz Scheduler * ({@code org.quartz.Scheduler}). This allows you to create new jobs * and triggers, and also to control and monitor the entire Scheduler.

*

*

Note that Quartz instantiates a new Job for each execution, in * contrast to Timer which uses a TimerTask instance that is shared * between repeated executions. Just JobDetail descriptors are shared.

* * @author Daniel Bordin * @see org.quartz.Scheduler * @see org.quartz.SchedulerFactory * @see org.quartz.impl.StdSchedulerFactory */ public class QuartzSingularSchedulerFactory extends SingularSchedulerAccessor { private Class schedulerFactoryClass = StdSchedulerFactory.class; private String schedulerName; private ResourceBundle configLocation; private Properties quartzProperties; private JobFactory jobFactory; private boolean jobFactorySet = false; private boolean exposeSchedulerInRepository = false; private boolean waitForJobsToCompleteOnShutdown = false; private Scheduler scheduler; /** * Set the name of the Scheduler to create via the SchedulerFactory. *

If not specified, the bean name will be used as default scheduler name. * * @see org.quartz.SchedulerFactory#getScheduler() * @see org.quartz.SchedulerFactory#getScheduler(String) */ @Override public void setSchedulerName(String schedulerName) { this.schedulerName = schedulerName; } /** * Set the location of the Quartz properties config file, for example * as classpath resource "classpath:quartz.properties". *

Note: Can be omitted when all necessary properties are specified * locally via this bean, or when relying on Quartz' default configuration. * * @see #setQuartzProperties */ @Override public void setConfigLocation(ResourceBundle configLocation) { this.configLocation = configLocation; } /** * Set Quartz properties, like "org.quartz.threadPool.class". *

Can be used to override values in a Quartz properties config file, * or to specify all necessary properties locally. * * @see #setConfigLocation */ @Override public void setQuartzProperties(Properties quartzProperties) { this.quartzProperties = quartzProperties; } /** * Set the Quartz JobFactory to use for this Scheduler. *

Default is {@link QuartzJobFactory}, which supports * {@link IScheduledJob} objects as well as standard Quartz * {@link org.quartz.Job} instances. Note that this default only applies * to a local Scheduler, not to a RemoteScheduler (where setting * a custom JobFactory is not supported by Quartz). * * @see QuartzJobFactory */ @Override public void setJobFactory(JobFactory jobFactory) { this.jobFactory = jobFactory; this.jobFactorySet = true; } /** * Set whether to expose the {@link Scheduler} instance in the * Quartz {@link SchedulerRepository}. Default is "false", since the * Scheduler is usually exclusively intended for access within the context. *

Switch this flag to "true" in order to expose the Scheduler globally. * This is not recommended unless you have an existing application that * relies on this behavior. */ @Override public void setExposeSchedulerInRepository(boolean exposeSchedulerInRepository) { this.exposeSchedulerInRepository = exposeSchedulerInRepository; } /** * Set whether to wait for running jobs to complete on shutdown. *

Default is "false". Switch this to "true" if you prefer * fully completed jobs at the expense of a longer shutdown phase. * * @see org.quartz.Scheduler#shutdown(boolean) */ @Override public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) { this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown; } /** * This method allows the instance to perform initialization only * possible when all bean properties have been set and to throw an * exception in the event of misconfiguration. * * @throws Exception in the event of misconfiguration (such * as failure to set an essential property) or if initialization fails. */ @Override public void initialize() throws SingularException { try { SchedulerFactory schedulerFactory = this.schedulerFactoryClass.newInstance(); initSchedulerFactory(schedulerFactory); this.scheduler = createScheduler(schedulerFactory, this.schedulerName); if (!this.jobFactorySet && !(this.scheduler instanceof RemoteScheduler)) { /* Use QuartzJobFactory as default for a local Scheduler, unless when * explicitly given a null value through the "jobFactory" property. */ this.jobFactory = new QuartzJobFactory(); } if (this.jobFactory != null) { this.scheduler.setJobFactory(this.jobFactory); } registerListeners(); registerJobsAndTriggers(); } catch (Exception e){ throw SingularException.rethrow(e.getMessage(), e); } } /** * Load and/or apply Quartz properties to the given SchedulerFactory. * * @param schedulerFactory */ private void initSchedulerFactory(SchedulerFactory schedulerFactory) throws SchedulerException, IOException { Properties mergedProps = new Properties(); mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName()); mergedProps.setProperty(SINGULAR_PROP_THREAD_COUNT, Integer.toString(SINGULAR_DEFAULT_THREAD_COUNT)); if (this.configLocation != null) { if (logger.isInfoEnabled()) { logger.info("Loading Quartz config from [" + this.configLocation + "]"); } this.fillProperties(mergedProps, this.configLocation); } this.mergePropertiesIntoMap(this.quartzProperties, mergedProps); // Make sure to set the scheduler name as configured in the Spring configuration. if (this.schedulerName != null) { mergedProps.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName); } ((StdSchedulerFactory) schedulerFactory).initialize(mergedProps); } /** * Fill the given properties from the given resource (in ISO-8859-1 encoding). * * @param mergedProps the Properties instance to fill * @param configLocation the resource to load from */ private void fillProperties(Properties mergedProps, ResourceBundle configLocation) { if (mergedProps == null) { throw new IllegalArgumentException("Map must not be null"); } if (configLocation != null) { for (Enumeration en = configLocation.getKeys(); en.hasMoreElements(); ) { String key = (String) en.nextElement(); Object value; try { value = configLocation.getString(key); } catch (MissingResourceException e) { logger.info(e.getMessage(), e); value = null; } assert value != null; mergedProps.put(key, value); } } } /** * Merge the given Properties instance into the given Map, * copying all properties (key-value pairs) over. *

Uses {@code Properties.propertyNames()} to even catch * default properties linked into the original Properties instance. * * @param quartzProperties the Properties instance to merge (may be {@code null}) * @param mergedProps the target Map to merge the properties into */ private void mergePropertiesIntoMap(Properties quartzProperties, Properties mergedProps) { if (mergedProps == null) { throw new IllegalArgumentException("Map must not be null"); } if (quartzProperties != null) { for (Enumeration en = quartzProperties.propertyNames(); en.hasMoreElements(); ) { String key = (String) en.nextElement(); String value = quartzProperties.getProperty(key); if (value == null) { value = quartzProperties.getProperty(key); } assert value != null; mergedProps.setProperty(key, value); } } } /** * Create the Scheduler instance for the given factory and scheduler name. * Called by {@link #initialize()}. *

The default implementation invokes SchedulerFactory's {@code getScheduler} * method. Can be overridden for custom Scheduler creation. * * * @param schedulerFactory * @param schedulerName the name of the scheduler to create * @return the Scheduler instance * * @throws SchedulerException if thrown by Quartz methods * @see #initialize() * @see org.quartz.SchedulerFactory#getScheduler */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName) throws SchedulerException { SchedulerRepository repository = SchedulerRepository.getInstance(); synchronized (repository) { Scheduler existingScheduler = (schedulerName != null ? repository.lookup(schedulerName) : null); Scheduler newScheduler = schedulerFactory.getScheduler(); if (newScheduler == existingScheduler) { throw new IllegalStateException("Active Scheduler of name '" + schedulerName + "' already registered " + "in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!"); } if (!this.exposeSchedulerInRepository) { // Need to remove it in this case, since Quartz shares the Scheduler instance by default! SchedulerRepository.getInstance().remove(newScheduler.getSchedulerName()); } return newScheduler; } } /** * Start the Quartz Scheduler, respecting the "startupDelay" setting. * * @param scheduler the Scheduler to start * @param startupDelay the number of seconds to wait before starting * the Scheduler asynchronously * @throws SchedulerException if could not start Quartz Scheduler. */ protected void startScheduler(final Scheduler scheduler, final int startupDelay) throws SchedulerException { if (startupDelay <= 0) { logger.info("Starting Quartz Scheduler now"); scheduler.start(); } else { if (logger.isInfoEnabled()) { logger.info("Will start Quartz Scheduler [" + scheduler.getSchedulerName() + "] in " + startupDelay + " seconds"); } Thread schedulerThread = new Thread() { @Override public void run() { try { Thread.sleep(startupDelay * 1000L); } catch (InterruptedException e) { logger.info(e.getMessage(), e); Thread.currentThread().interrupt(); } if (logger.isInfoEnabled()) { logger.info("Starting Quartz Scheduler now, after delay of " + startupDelay + " seconds"); } try { scheduler.start(); } catch (SchedulerException e) { logger.error(e.getMessage(), e); throw SingularException.rethrow(e.getMessage(), e); } } }; schedulerThread.setName("Quartz Scheduler [" + scheduler.getSchedulerName() + "]"); schedulerThread.setDaemon(true); schedulerThread.start(); } } /** * Method that determines the Scheduler to operate on. */ @Override public Scheduler getScheduler() { return this.scheduler; } /** * Start the scheduler immediately. * * @throws SchedulerException if could not start Quartz Scheduler. */ @Override public void start() throws SchedulerException { start(0); } /** * Start the scheduler, respecting the "startupDelay" setting. * * @param startupDelay the number of seconds to wait before starting * @throws SchedulerException if could not start Quartz Scheduler. */ @Override public void start(int startupDelay) throws SchedulerException { if (this.scheduler != null) { startScheduler(this.scheduler, startupDelay); } } /** * Temporarily halts the Scheduler's firing of {@link Trigger}s. * *

* When start() is called (to bring the scheduler out of * stand-by mode), trigger misfire instructions will NOT be applied * during the execution of the start() method - any misfires * will be detected immediately afterward (by the JobStore's * normal process). *

* *

* The scheduler is not destroyed, and can be re-started at any time. *

* * @see #start() */ @Override public void stop() throws SchedulerException { if (this.scheduler != null) { this.scheduler.standby(); } } /** * Call {@code callback.run()} after {@link #stop()}. * * @param callback the callback. * @throws SchedulerException if could not start Quartz Scheduler. */ @Override public void stop(Runnable callback) throws SchedulerException { stop(); callback.run(); } /** * Reports whether the Scheduler is in stand-by mode. * * @see #stop() * @see #start() */ @Override public boolean isRunning() { if (this.scheduler != null) { try { return !this.scheduler.isInStandbyMode(); } catch (SchedulerException e) { logger.info(e.getMessage(), e); return false; } } return false; } /** * Shut down the Quartz scheduler on factory shutdown, * stopping all scheduled jobs. */ @Override public void destroy() throws SchedulerException { logger.info("Shutting down Quartz Scheduler"); this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown); } /** * Add a job. * * @param jobDetail the job detail. * @throws SchedulerException if could not start Quartz Scheduler. */ @Override public void addJob(JobDetail jobDetail) throws SchedulerException { addJobToScheduler(jobDetail); } /** * Add a trigger with the specified job detail. * * @param trigger the trigger. * @param jobDetail the job detail. * @throws SchedulerException if could not start Quartz Scheduler. */ @Override public void addTrigger(Trigger trigger, JobDetail jobDetail) throws SchedulerException { trigger.getJobDataMap().put(SINGULAR_JOB_DETAIL_KEY, jobDetail); addTriggerToScheduler(trigger); } /** * Add trigger and the trigger's job detail. * * @param trigger the trigger. * @throws SchedulerException if could not start Quartz Scheduler. */ @Override public void addTrigger(Trigger trigger) throws SchedulerException { addJobToScheduler((JobDetail) trigger.getJobDataMap().get(SINGULAR_JOB_DETAIL_KEY)); addTriggerToScheduler(trigger); } /** * Trigger the identified {@link org.quartz.JobDetail} (execute it now). * * @param jobKey * @throws SchedulerException */ @Override public void triggerJob(JobKey jobKey) throws SchedulerException{ getScheduler().triggerJob(jobKey); } public boolean deleteJob(JobKey jobKey) throws SchedulerException { return getScheduler().deleteJob(jobKey); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy