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}