001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.scheduling.quartz;
018
019import java.io.IOException;
020import java.util.Map;
021import java.util.Properties;
022import java.util.concurrent.Executor;
023import java.util.concurrent.TimeUnit;
024
025import javax.sql.DataSource;
026
027import org.quartz.Scheduler;
028import org.quartz.SchedulerException;
029import org.quartz.SchedulerFactory;
030import org.quartz.impl.RemoteScheduler;
031import org.quartz.impl.SchedulerRepository;
032import org.quartz.impl.StdSchedulerFactory;
033import org.quartz.simpl.SimpleThreadPool;
034import org.quartz.spi.JobFactory;
035
036import org.springframework.beans.BeanUtils;
037import org.springframework.beans.factory.BeanNameAware;
038import org.springframework.beans.factory.DisposableBean;
039import org.springframework.beans.factory.FactoryBean;
040import org.springframework.beans.factory.InitializingBean;
041import org.springframework.context.ApplicationContext;
042import org.springframework.context.ApplicationContextAware;
043import org.springframework.context.SmartLifecycle;
044import org.springframework.core.io.Resource;
045import org.springframework.core.io.ResourceLoader;
046import org.springframework.core.io.support.PropertiesLoaderUtils;
047import org.springframework.lang.Nullable;
048import org.springframework.scheduling.SchedulingException;
049import org.springframework.util.Assert;
050import org.springframework.util.CollectionUtils;
051
052/**
053 * {@link FactoryBean} that creates and configures a Quartz {@link org.quartz.Scheduler},
054 * manages its lifecycle as part of the Spring application context, and exposes the
055 * Scheduler as bean reference for dependency injection.
056 *
057 * <p>Allows registration of JobDetails, Calendars and Triggers, automatically
058 * starting the scheduler on initialization and shutting it down on destruction.
059 * In scenarios that just require static registration of jobs at startup, there
060 * is no need to access the Scheduler instance itself in application code.
061 *
062 * <p>For dynamic registration of jobs at runtime, use a bean reference to
063 * this SchedulerFactoryBean to get direct access to the Quartz Scheduler
064 * ({@code org.quartz.Scheduler}). This allows you to create new jobs
065 * and triggers, and also to control and monitor the entire Scheduler.
066 *
067 * <p>Note that Quartz instantiates a new Job for each execution, in
068 * contrast to Timer which uses a TimerTask instance that is shared
069 * between repeated executions. Just JobDetail descriptors are shared.
070 *
071 * <p>When using persistent jobs, it is strongly recommended to perform all
072 * operations on the Scheduler within Spring-managed (or plain JTA) transactions.
073 * Else, database locking will not properly work and might even break.
074 * (See {@link #setDataSource setDataSource} javadoc for details.)
075 *
076 * <p>The preferred way to achieve transactional execution is to demarcate
077 * declarative transactions at the business facade level, which will
078 * automatically apply to Scheduler operations performed within those scopes.
079 * Alternatively, you may add transactional advice for the Scheduler itself.
080 *
081 * <p>Compatible with Quartz 2.1.4 and higher, as of Spring 4.1.
082 *
083 * @author Juergen Hoeller
084 * @since 18.02.2004
085 * @see #setDataSource
086 * @see org.quartz.Scheduler
087 * @see org.quartz.SchedulerFactory
088 * @see org.quartz.impl.StdSchedulerFactory
089 * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean
090 */
091public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>,
092                BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
093
094        /**
095         * The thread count property.
096         */
097        public static final String PROP_THREAD_COUNT = "org.quartz.threadPool.threadCount";
098
099        /**
100         * The default thread count.
101         */
102        public static final int DEFAULT_THREAD_COUNT = 10;
103
104
105        private static final ThreadLocal<ResourceLoader> configTimeResourceLoaderHolder = new ThreadLocal<>();
106
107        private static final ThreadLocal<Executor> configTimeTaskExecutorHolder = new ThreadLocal<>();
108
109        private static final ThreadLocal<DataSource> configTimeDataSourceHolder = new ThreadLocal<>();
110
111        private static final ThreadLocal<DataSource> configTimeNonTransactionalDataSourceHolder = new ThreadLocal<>();
112
113
114        /**
115         * Return the {@link ResourceLoader} for the currently configured Quartz Scheduler,
116         * to be used by {@link ResourceLoaderClassLoadHelper}.
117         * <p>This instance will be set before initialization of the corresponding Scheduler,
118         * and reset immediately afterwards. It is thus only available during configuration.
119         * @see #setApplicationContext
120         * @see ResourceLoaderClassLoadHelper
121         */
122        @Nullable
123        public static ResourceLoader getConfigTimeResourceLoader() {
124                return configTimeResourceLoaderHolder.get();
125        }
126
127        /**
128         * Return the {@link Executor} for the currently configured Quartz Scheduler,
129         * to be used by {@link LocalTaskExecutorThreadPool}.
130         * <p>This instance will be set before initialization of the corresponding Scheduler,
131         * and reset immediately afterwards. It is thus only available during configuration.
132         * @since 2.0
133         * @see #setTaskExecutor
134         * @see LocalTaskExecutorThreadPool
135         */
136        @Nullable
137        public static Executor getConfigTimeTaskExecutor() {
138                return configTimeTaskExecutorHolder.get();
139        }
140
141        /**
142         * Return the {@link DataSource} for the currently configured Quartz Scheduler,
143         * to be used by {@link LocalDataSourceJobStore}.
144         * <p>This instance will be set before initialization of the corresponding Scheduler,
145         * and reset immediately afterwards. It is thus only available during configuration.
146         * @since 1.1
147         * @see #setDataSource
148         * @see LocalDataSourceJobStore
149         */
150        @Nullable
151        public static DataSource getConfigTimeDataSource() {
152                return configTimeDataSourceHolder.get();
153        }
154
155        /**
156         * Return the non-transactional {@link DataSource} for the currently configured
157         * Quartz Scheduler, to be used by {@link LocalDataSourceJobStore}.
158         * <p>This instance will be set before initialization of the corresponding Scheduler,
159         * and reset immediately afterwards. It is thus only available during configuration.
160         * @since 1.1
161         * @see #setNonTransactionalDataSource
162         * @see LocalDataSourceJobStore
163         */
164        @Nullable
165        public static DataSource getConfigTimeNonTransactionalDataSource() {
166                return configTimeNonTransactionalDataSourceHolder.get();
167        }
168
169
170        @Nullable
171        private SchedulerFactory schedulerFactory;
172
173        private Class<? extends SchedulerFactory> schedulerFactoryClass = StdSchedulerFactory.class;
174
175        @Nullable
176        private String schedulerName;
177
178        @Nullable
179        private Resource configLocation;
180
181        @Nullable
182        private Properties quartzProperties;
183
184        @Nullable
185        private Executor taskExecutor;
186
187        @Nullable
188        private DataSource dataSource;
189
190        @Nullable
191        private DataSource nonTransactionalDataSource;
192
193        @Nullable
194        private Map<String, ?> schedulerContextMap;
195
196        @Nullable
197        private String applicationContextSchedulerContextKey;
198
199        @Nullable
200        private JobFactory jobFactory;
201
202        private boolean jobFactorySet = false;
203
204        private boolean autoStartup = true;
205
206        private int startupDelay = 0;
207
208        private int phase = DEFAULT_PHASE;
209
210        private boolean exposeSchedulerInRepository = false;
211
212        private boolean waitForJobsToCompleteOnShutdown = false;
213
214        @Nullable
215        private String beanName;
216
217        @Nullable
218        private ApplicationContext applicationContext;
219
220        @Nullable
221        private Scheduler scheduler;
222
223
224        /**
225         * Set an external Quartz {@link SchedulerFactory} instance to use.
226         * <p>Default is an internal {@link StdSchedulerFactory} instance. If this method is
227         * called, it overrides any class specified through {@link #setSchedulerFactoryClass}
228         * as well as any settings specified through {@link #setConfigLocation},
229         * {@link #setQuartzProperties}, {@link #setTaskExecutor} or {@link #setDataSource}.
230         * <p><b>NOTE:</b> With an externally provided {@code SchedulerFactory} instance,
231         * local settings such as {@link #setConfigLocation} or {@link #setQuartzProperties}
232         * will be ignored here in {@code SchedulerFactoryBean}, expecting the external
233         * {@code SchedulerFactory} instance to get initialized on its own.
234         * @since 4.3.15
235         * @see #setSchedulerFactoryClass
236         */
237        public void setSchedulerFactory(SchedulerFactory schedulerFactory) {
238                this.schedulerFactory = schedulerFactory;
239        }
240
241        /**
242         * Set the Quartz {@link SchedulerFactory} implementation to use.
243         * <p>Default is the {@link StdSchedulerFactory} class, reading in the standard
244         * {@code quartz.properties} from {@code quartz.jar}. For applying custom Quartz
245         * properties, specify {@link #setConfigLocation "configLocation"} and/or
246         * {@link #setQuartzProperties "quartzProperties"} etc on this local
247         * {@code SchedulerFactoryBean} instance.
248         * @see org.quartz.impl.StdSchedulerFactory
249         * @see #setConfigLocation
250         * @see #setQuartzProperties
251         * @see #setTaskExecutor
252         * @see #setDataSource
253         */
254        public void setSchedulerFactoryClass(Class<? extends SchedulerFactory> schedulerFactoryClass) {
255                this.schedulerFactoryClass = schedulerFactoryClass;
256        }
257
258        /**
259         * Set the name of the Scheduler to create via the SchedulerFactory, as an
260         * alternative to the {@code org.quartz.scheduler.instanceName} property.
261         * <p>If not specified, the name will be taken from Quartz properties
262         * ({@code org.quartz.scheduler.instanceName}), or from the declared
263         * {@code SchedulerFactoryBean} bean name as a fallback.
264         * @see #setBeanName
265         * @see StdSchedulerFactory#PROP_SCHED_INSTANCE_NAME
266         * @see org.quartz.SchedulerFactory#getScheduler()
267         * @see org.quartz.SchedulerFactory#getScheduler(String)
268         */
269        public void setSchedulerName(String schedulerName) {
270                this.schedulerName = schedulerName;
271        }
272
273        /**
274         * Set the location of the Quartz properties config file, for example
275         * as classpath resource "classpath:quartz.properties".
276         * <p>Note: Can be omitted when all necessary properties are specified
277         * locally via this bean, or when relying on Quartz' default configuration.
278         * @see #setQuartzProperties
279         */
280        public void setConfigLocation(Resource configLocation) {
281                this.configLocation = configLocation;
282        }
283
284        /**
285         * Set Quartz properties, like "org.quartz.threadPool.class".
286         * <p>Can be used to override values in a Quartz properties config file,
287         * or to specify all necessary properties locally.
288         * @see #setConfigLocation
289         */
290        public void setQuartzProperties(Properties quartzProperties) {
291                this.quartzProperties = quartzProperties;
292        }
293
294        /**
295         * Set a Spring-managed {@link Executor} to use as Quartz backend.
296         * Exposed as thread pool through the Quartz SPI.
297         * <p>Can be used to assign a local JDK ThreadPoolExecutor or a CommonJ
298         * WorkManager as Quartz backend, to avoid Quartz's manual thread creation.
299         * <p>By default, a Quartz SimpleThreadPool will be used, configured through
300         * the corresponding Quartz properties.
301         * @since 2.0
302         * @see #setQuartzProperties
303         * @see LocalTaskExecutorThreadPool
304         * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
305         * @see org.springframework.scheduling.concurrent.DefaultManagedTaskExecutor
306         */
307        public void setTaskExecutor(Executor taskExecutor) {
308                this.taskExecutor = taskExecutor;
309        }
310
311        /**
312         * Set the default {@link DataSource} to be used by the Scheduler.
313         * If set, this will override corresponding settings in Quartz properties.
314         * <p>Note: If this is set, the Quartz settings should not define
315         * a job store "dataSource" to avoid meaningless double configuration.
316         * <p>A Spring-specific subclass of Quartz' JobStoreCMT will be used.
317         * It is therefore strongly recommended to perform all operations on
318         * the Scheduler within Spring-managed (or plain JTA) transactions.
319         * Else, database locking will not properly work and might even break
320         * (e.g. if trying to obtain a lock on Oracle without a transaction).
321         * <p>Supports both transactional and non-transactional DataSource access.
322         * With a non-XA DataSource and local Spring transactions, a single DataSource
323         * argument is sufficient. In case of an XA DataSource and global JTA transactions,
324         * SchedulerFactoryBean's "nonTransactionalDataSource" property should be set,
325         * passing in a non-XA DataSource that will not participate in global transactions.
326         * @since 1.1
327         * @see #setNonTransactionalDataSource
328         * @see #setQuartzProperties
329         * @see #setTransactionManager
330         * @see LocalDataSourceJobStore
331         */
332        public void setDataSource(DataSource dataSource) {
333                this.dataSource = dataSource;
334        }
335
336        /**
337         * Set the {@link DataSource} to be used <i>for non-transactional access</i>.
338         * <p>This is only necessary if the default DataSource is an XA DataSource that will
339         * always participate in transactions: A non-XA version of that DataSource should
340         * be specified as "nonTransactionalDataSource" in such a scenario.
341         * <p>This is not relevant with a local DataSource instance and Spring transactions.
342         * Specifying a single default DataSource as "dataSource" is sufficient there.
343         * @since 1.1
344         * @see #setDataSource
345         * @see LocalDataSourceJobStore
346         */
347        public void setNonTransactionalDataSource(DataSource nonTransactionalDataSource) {
348                this.nonTransactionalDataSource = nonTransactionalDataSource;
349        }
350
351        /**
352         * Register objects in the Scheduler context via a given Map.
353         * These objects will be available to any Job that runs in this Scheduler.
354         * <p>Note: When using persistent Jobs whose JobDetail will be kept in the
355         * database, do not put Spring-managed beans or an ApplicationContext
356         * reference into the JobDataMap but rather into the SchedulerContext.
357         * @param schedulerContextAsMap a Map with String keys and any objects as
358         * values (for example Spring-managed beans)
359         * @see JobDetailFactoryBean#setJobDataAsMap
360         */
361        public void setSchedulerContextAsMap(Map<String, ?> schedulerContextAsMap) {
362                this.schedulerContextMap = schedulerContextAsMap;
363        }
364
365        /**
366         * Set the key of an {@link ApplicationContext} reference to expose in the
367         * SchedulerContext, for example "applicationContext". Default is none.
368         * Only applicable when running in a Spring ApplicationContext.
369         * <p>Note: When using persistent Jobs whose JobDetail will be kept in the
370         * database, do not put an ApplicationContext reference into the JobDataMap
371         * but rather into the SchedulerContext.
372         * <p>In case of a QuartzJobBean, the reference will be applied to the Job
373         * instance as bean property. An "applicationContext" attribute will
374         * correspond to a "setApplicationContext" method in that scenario.
375         * <p>Note that BeanFactory callback interfaces like ApplicationContextAware
376         * are not automatically applied to Quartz Job instances, because Quartz
377         * itself is responsible for the lifecycle of its Jobs.
378         * @see JobDetailFactoryBean#setApplicationContextJobDataKey
379         * @see org.springframework.context.ApplicationContext
380         */
381        public void setApplicationContextSchedulerContextKey(String applicationContextSchedulerContextKey) {
382                this.applicationContextSchedulerContextKey = applicationContextSchedulerContextKey;
383        }
384
385        /**
386         * Set the Quartz {@link JobFactory} to use for this Scheduler.
387         * <p>Default is Spring's {@link AdaptableJobFactory}, which supports
388         * {@link java.lang.Runnable} objects as well as standard Quartz
389         * {@link org.quartz.Job} instances. Note that this default only applies
390         * to a <i>local</i> Scheduler, not to a RemoteScheduler (where setting
391         * a custom JobFactory is not supported by Quartz).
392         * <p>Specify an instance of Spring's {@link SpringBeanJobFactory} here
393         * (typically as an inner bean definition) to automatically populate a job's
394         * bean properties from the specified job data map and scheduler context.
395         * @since 2.0
396         * @see AdaptableJobFactory
397         * @see SpringBeanJobFactory
398         */
399        public void setJobFactory(JobFactory jobFactory) {
400                this.jobFactory = jobFactory;
401                this.jobFactorySet = true;
402        }
403
404        /**
405         * Set whether to automatically start the scheduler after initialization.
406         * <p>Default is "true"; set this to "false" to allow for manual startup.
407         */
408        public void setAutoStartup(boolean autoStartup) {
409                this.autoStartup = autoStartup;
410        }
411
412        /**
413         * Return whether this scheduler is configured for auto-startup. If "true",
414         * the scheduler will start after the context is refreshed and after the
415         * start delay, if any.
416         */
417        @Override
418        public boolean isAutoStartup() {
419                return this.autoStartup;
420        }
421
422        /**
423         * Specify the phase in which this scheduler should be started and stopped.
424         * The startup order proceeds from lowest to highest, and the shutdown order
425         * is the reverse of that. By default this value is {@code Integer.MAX_VALUE}
426         * meaning that this scheduler starts as late as possible and stops as soon
427         * as possible.
428         * @since 3.0
429         */
430        public void setPhase(int phase) {
431                this.phase = phase;
432        }
433
434        /**
435         * Return the phase in which this scheduler will be started and stopped.
436         */
437        @Override
438        public int getPhase() {
439                return this.phase;
440        }
441
442        /**
443         * Set the number of seconds to wait after initialization before
444         * starting the scheduler asynchronously. Default is 0, meaning
445         * immediate synchronous startup on initialization of this bean.
446         * <p>Setting this to 10 or 20 seconds makes sense if no jobs
447         * should be run before the entire application has started up.
448         */
449        public void setStartupDelay(int startupDelay) {
450                this.startupDelay = startupDelay;
451        }
452
453        /**
454         * Set whether to expose the Spring-managed {@link Scheduler} instance in the
455         * Quartz {@link SchedulerRepository}. Default is "false", since the Spring-managed
456         * Scheduler is usually exclusively intended for access within the Spring context.
457         * <p>Switch this flag to "true" in order to expose the Scheduler globally.
458         * This is not recommended unless you have an existing Spring application that
459         * relies on this behavior. Note that such global exposure was the accidental
460         * default in earlier Spring versions; this has been fixed as of Spring 2.5.6.
461         */
462        public void setExposeSchedulerInRepository(boolean exposeSchedulerInRepository) {
463                this.exposeSchedulerInRepository = exposeSchedulerInRepository;
464        }
465
466        /**
467         * Set whether to wait for running jobs to complete on shutdown.
468         * <p>Default is "false". Switch this to "true" if you prefer
469         * fully completed jobs at the expense of a longer shutdown phase.
470         * @see org.quartz.Scheduler#shutdown(boolean)
471         */
472        public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
473                this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
474        }
475
476        @Override
477        public void setBeanName(String name) {
478                this.beanName = name;
479        }
480
481        @Override
482        public void setApplicationContext(ApplicationContext applicationContext) {
483                this.applicationContext = applicationContext;
484        }
485
486
487        //---------------------------------------------------------------------
488        // Implementation of InitializingBean interface
489        //---------------------------------------------------------------------
490
491        @Override
492        public void afterPropertiesSet() throws Exception {
493                if (this.dataSource == null && this.nonTransactionalDataSource != null) {
494                        this.dataSource = this.nonTransactionalDataSource;
495                }
496
497                if (this.applicationContext != null && this.resourceLoader == null) {
498                        this.resourceLoader = this.applicationContext;
499                }
500
501                // Initialize the Scheduler instance...
502                this.scheduler = prepareScheduler(prepareSchedulerFactory());
503                try {
504                        registerListeners();
505                        registerJobsAndTriggers();
506                }
507                catch (Exception ex) {
508                        try {
509                                this.scheduler.shutdown(true);
510                        }
511                        catch (Exception ex2) {
512                                logger.debug("Scheduler shutdown exception after registration failure", ex2);
513                        }
514                        throw ex;
515                }
516        }
517
518
519        /**
520         * Create a SchedulerFactory if necessary and apply locally defined Quartz properties to it.
521         * @return the initialized SchedulerFactory
522         */
523        private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
524                SchedulerFactory schedulerFactory = this.schedulerFactory;
525                if (schedulerFactory == null) {
526                        // Create local SchedulerFactory instance (typically a StdSchedulerFactory)
527                        schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
528                        if (schedulerFactory instanceof StdSchedulerFactory) {
529                                initSchedulerFactory((StdSchedulerFactory) schedulerFactory);
530                        }
531                        else if (this.configLocation != null || this.quartzProperties != null ||
532                                        this.taskExecutor != null || this.dataSource != null) {
533                                throw new IllegalArgumentException(
534                                                "StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory);
535                        }
536                        // Otherwise, no local settings to be applied via StdSchedulerFactory.initialize(Properties)
537                }
538                // Otherwise, assume that externally provided factory has been initialized with appropriate settings
539                return schedulerFactory;
540        }
541
542        /**
543         * Initialize the given SchedulerFactory, applying locally defined Quartz properties to it.
544         * @param schedulerFactory the SchedulerFactory to initialize
545         */
546        private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws SchedulerException, IOException {
547                Properties mergedProps = new Properties();
548                if (this.resourceLoader != null) {
549                        mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_CLASS_LOAD_HELPER_CLASS,
550                                        ResourceLoaderClassLoadHelper.class.getName());
551                }
552
553                if (this.taskExecutor != null) {
554                        mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS,
555                                        LocalTaskExecutorThreadPool.class.getName());
556                }
557                else {
558                        // Set necessary default properties here, as Quartz will not apply
559                        // its default configuration when explicitly given properties.
560                        mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName());
561                        mergedProps.setProperty(PROP_THREAD_COUNT, Integer.toString(DEFAULT_THREAD_COUNT));
562                }
563
564                if (this.configLocation != null) {
565                        if (logger.isDebugEnabled()) {
566                                logger.debug("Loading Quartz config from [" + this.configLocation + "]");
567                        }
568                        PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation);
569                }
570
571                CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps);
572                if (this.dataSource != null) {
573                        mergedProps.setProperty(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName());
574                }
575
576                // Determine scheduler name across local settings and Quartz properties...
577                if (this.schedulerName != null) {
578                        mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName);
579                }
580                else {
581                        String nameProp = mergedProps.getProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME);
582                        if (nameProp != null) {
583                                this.schedulerName = nameProp;
584                        }
585                        else if (this.beanName != null) {
586                                mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.beanName);
587                                this.schedulerName = this.beanName;
588                        }
589                }
590
591                schedulerFactory.initialize(mergedProps);
592        }
593
594        private Scheduler prepareScheduler(SchedulerFactory schedulerFactory) throws SchedulerException {
595                if (this.resourceLoader != null) {
596                        // Make given ResourceLoader available for SchedulerFactory configuration.
597                        configTimeResourceLoaderHolder.set(this.resourceLoader);
598                }
599                if (this.taskExecutor != null) {
600                        // Make given TaskExecutor available for SchedulerFactory configuration.
601                        configTimeTaskExecutorHolder.set(this.taskExecutor);
602                }
603                if (this.dataSource != null) {
604                        // Make given DataSource available for SchedulerFactory configuration.
605                        configTimeDataSourceHolder.set(this.dataSource);
606                }
607                if (this.nonTransactionalDataSource != null) {
608                        // Make given non-transactional DataSource available for SchedulerFactory configuration.
609                        configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource);
610                }
611
612                // Get Scheduler instance from SchedulerFactory.
613                try {
614                        Scheduler scheduler = createScheduler(schedulerFactory, this.schedulerName);
615                        populateSchedulerContext(scheduler);
616
617                        if (!this.jobFactorySet && !(scheduler instanceof RemoteScheduler)) {
618                                // Use AdaptableJobFactory as default for a local Scheduler, unless when
619                                // explicitly given a null value through the "jobFactory" bean property.
620                                this.jobFactory = new AdaptableJobFactory();
621                        }
622                        if (this.jobFactory != null) {
623                                if (this.applicationContext != null && this.jobFactory instanceof ApplicationContextAware) {
624                                        ((ApplicationContextAware) this.jobFactory).setApplicationContext(this.applicationContext);
625                                }
626                                if (this.jobFactory instanceof SchedulerContextAware) {
627                                        ((SchedulerContextAware) this.jobFactory).setSchedulerContext(scheduler.getContext());
628                                }
629                                scheduler.setJobFactory(this.jobFactory);
630                        }
631                        return scheduler;
632                }
633
634                finally {
635                        if (this.resourceLoader != null) {
636                                configTimeResourceLoaderHolder.remove();
637                        }
638                        if (this.taskExecutor != null) {
639                                configTimeTaskExecutorHolder.remove();
640                        }
641                        if (this.dataSource != null) {
642                                configTimeDataSourceHolder.remove();
643                        }
644                        if (this.nonTransactionalDataSource != null) {
645                                configTimeNonTransactionalDataSourceHolder.remove();
646                        }
647                }
648        }
649
650        /**
651         * Create the Scheduler instance for the given factory and scheduler name.
652         * Called by {@link #afterPropertiesSet}.
653         * <p>The default implementation invokes SchedulerFactory's {@code getScheduler}
654         * method. Can be overridden for custom Scheduler creation.
655         * @param schedulerFactory the factory to create the Scheduler with
656         * @param schedulerName the name of the scheduler to create
657         * @return the Scheduler instance
658         * @throws SchedulerException if thrown by Quartz methods
659         * @see #afterPropertiesSet
660         * @see org.quartz.SchedulerFactory#getScheduler
661         */
662        protected Scheduler createScheduler(SchedulerFactory schedulerFactory, @Nullable String schedulerName)
663                        throws SchedulerException {
664
665                // Override thread context ClassLoader to work around naive Quartz ClassLoadHelper loading.
666                Thread currentThread = Thread.currentThread();
667                ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
668                boolean overrideClassLoader = (this.resourceLoader != null &&
669                                this.resourceLoader.getClassLoader() != threadContextClassLoader);
670                if (overrideClassLoader) {
671                        currentThread.setContextClassLoader(this.resourceLoader.getClassLoader());
672                }
673                try {
674                        SchedulerRepository repository = SchedulerRepository.getInstance();
675                        synchronized (repository) {
676                                Scheduler existingScheduler = (schedulerName != null ? repository.lookup(schedulerName) : null);
677                                Scheduler newScheduler = schedulerFactory.getScheduler();
678                                if (newScheduler == existingScheduler) {
679                                        throw new IllegalStateException("Active Scheduler of name '" + schedulerName + "' already registered " +
680                                                        "in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!");
681                                }
682                                if (!this.exposeSchedulerInRepository) {
683                                        // Need to remove it in this case, since Quartz shares the Scheduler instance by default!
684                                        SchedulerRepository.getInstance().remove(newScheduler.getSchedulerName());
685                                }
686                                return newScheduler;
687                        }
688                }
689                finally {
690                        if (overrideClassLoader) {
691                                // Reset original thread context ClassLoader.
692                                currentThread.setContextClassLoader(threadContextClassLoader);
693                        }
694                }
695        }
696
697        /**
698         * Expose the specified context attributes and/or the current
699         * ApplicationContext in the Quartz SchedulerContext.
700         */
701        private void populateSchedulerContext(Scheduler scheduler) throws SchedulerException {
702                // Put specified objects into Scheduler context.
703                if (this.schedulerContextMap != null) {
704                        scheduler.getContext().putAll(this.schedulerContextMap);
705                }
706
707                // Register ApplicationContext in Scheduler context.
708                if (this.applicationContextSchedulerContextKey != null) {
709                        if (this.applicationContext == null) {
710                                throw new IllegalStateException(
711                                        "SchedulerFactoryBean needs to be set up in an ApplicationContext " +
712                                        "to be able to handle an 'applicationContextSchedulerContextKey'");
713                        }
714                        scheduler.getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext);
715                }
716        }
717
718
719        /**
720         * Start the Quartz Scheduler, respecting the "startupDelay" setting.
721         * @param scheduler the Scheduler to start
722         * @param startupDelay the number of seconds to wait before starting
723         * the Scheduler asynchronously
724         */
725        protected void startScheduler(final Scheduler scheduler, final int startupDelay) throws SchedulerException {
726                if (startupDelay <= 0) {
727                        logger.info("Starting Quartz Scheduler now");
728                        scheduler.start();
729                }
730                else {
731                        if (logger.isInfoEnabled()) {
732                                logger.info("Will start Quartz Scheduler [" + scheduler.getSchedulerName() +
733                                                "] in " + startupDelay + " seconds");
734                        }
735                        // Not using the Quartz startDelayed method since we explicitly want a daemon
736                        // thread here, not keeping the JVM alive in case of all other threads ending.
737                        Thread schedulerThread = new Thread() {
738                                @Override
739                                public void run() {
740                                        try {
741                                                TimeUnit.SECONDS.sleep(startupDelay);
742                                        }
743                                        catch (InterruptedException ex) {
744                                                Thread.currentThread().interrupt();
745                                                // simply proceed
746                                        }
747                                        if (logger.isInfoEnabled()) {
748                                                logger.info("Starting Quartz Scheduler now, after delay of " + startupDelay + " seconds");
749                                        }
750                                        try {
751                                                scheduler.start();
752                                        }
753                                        catch (SchedulerException ex) {
754                                                throw new SchedulingException("Could not start Quartz Scheduler after delay", ex);
755                                        }
756                                }
757                        };
758                        schedulerThread.setName("Quartz Scheduler [" + scheduler.getSchedulerName() + "]");
759                        schedulerThread.setDaemon(true);
760                        schedulerThread.start();
761                }
762        }
763
764
765        //---------------------------------------------------------------------
766        // Implementation of FactoryBean interface
767        //---------------------------------------------------------------------
768
769        @Override
770        public Scheduler getScheduler() {
771                Assert.state(this.scheduler != null, "No Scheduler set");
772                return this.scheduler;
773        }
774
775        @Override
776        @Nullable
777        public Scheduler getObject() {
778                return this.scheduler;
779        }
780
781        @Override
782        public Class<? extends Scheduler> getObjectType() {
783                return (this.scheduler != null ? this.scheduler.getClass() : Scheduler.class);
784        }
785
786        @Override
787        public boolean isSingleton() {
788                return true;
789        }
790
791
792        //---------------------------------------------------------------------
793        // Implementation of SmartLifecycle interface
794        //---------------------------------------------------------------------
795
796        @Override
797        public void start() throws SchedulingException {
798                if (this.scheduler != null) {
799                        try {
800                                startScheduler(this.scheduler, this.startupDelay);
801                        }
802                        catch (SchedulerException ex) {
803                                throw new SchedulingException("Could not start Quartz Scheduler", ex);
804                        }
805                }
806        }
807
808        @Override
809        public void stop() throws SchedulingException {
810                if (this.scheduler != null) {
811                        try {
812                                this.scheduler.standby();
813                        }
814                        catch (SchedulerException ex) {
815                                throw new SchedulingException("Could not stop Quartz Scheduler", ex);
816                        }
817                }
818        }
819
820        @Override
821        public boolean isRunning() throws SchedulingException {
822                if (this.scheduler != null) {
823                        try {
824                                return !this.scheduler.isInStandbyMode();
825                        }
826                        catch (SchedulerException ex) {
827                                return false;
828                        }
829                }
830                return false;
831        }
832
833
834        //---------------------------------------------------------------------
835        // Implementation of DisposableBean interface
836        //---------------------------------------------------------------------
837
838        /**
839         * Shut down the Quartz scheduler on bean factory shutdown,
840         * stopping all scheduled jobs.
841         */
842        @Override
843        public void destroy() throws SchedulerException {
844                if (this.scheduler != null) {
845                        logger.info("Shutting down Quartz Scheduler");
846                        this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown);
847                }
848        }
849
850}