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}