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