1 /*
2 * Copyright 2002-2008 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.springframework.scheduling.quartz;
18
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Iterator;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Properties;
27
28 import javax.sql.DataSource;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.quartz.Calendar;
33 import org.quartz.JobDetail;
34 import org.quartz.JobListener;
35 import org.quartz.ObjectAlreadyExistsException;
36 import org.quartz.Scheduler;
37 import org.quartz.SchedulerException;
38 import org.quartz.SchedulerFactory;
39 import org.quartz.SchedulerListener;
40 import org.quartz.Trigger;
41 import org.quartz.TriggerListener;
42 import org.quartz.impl.RemoteScheduler;
43 import org.quartz.impl.StdSchedulerFactory;
44 import org.quartz.simpl.SimpleThreadPool;
45 import org.quartz.spi.ClassLoadHelper;
46 import org.quartz.spi.JobFactory;
47 import org.quartz.xml.JobSchedulingDataProcessor;
48
49 import org.springframework.beans.BeanUtils;
50 import org.springframework.beans.factory.DisposableBean;
51 import org.springframework.beans.factory.FactoryBean;
52 import org.springframework.beans.factory.InitializingBean;
53 import org.springframework.context.ApplicationContext;
54 import org.springframework.context.ApplicationContextAware;
55 import org.springframework.context.Lifecycle;
56 import org.springframework.context.ResourceLoaderAware;
57 import org.springframework.core.io.Resource;
58 import org.springframework.core.io.ResourceLoader;
59 import org.springframework.core.io.support.PropertiesLoaderUtils;
60 import org.springframework.core.task.TaskExecutor;
61 import org.springframework.scheduling.SchedulingException;
62 import org.springframework.transaction.PlatformTransactionManager;
63 import org.springframework.transaction.TransactionException;
64 import org.springframework.transaction.TransactionStatus;
65 import org.springframework.transaction.support.DefaultTransactionDefinition;
66 import org.springframework.util.CollectionUtils;
67
68 /**
69 * FactoryBean that sets up a Quartz {@link org.quartz.Scheduler},
70 * manages its lifecycle as part of the Spring application context,
71 * and exposes the Scheduler reference for dependency injection.
72 *
73 * <p>Allows registration of JobDetails, Calendars and Triggers, automatically
74 * starting the scheduler on initialization and shutting it down on destruction.
75 * In scenarios that just require static registration of jobs at startup, there
76 * is no need to access the Scheduler instance itself in application code.
77 *
78 * <p>For dynamic registration of jobs at runtime, use a bean reference to
79 * this SchedulerFactoryBean to get direct access to the Quartz Scheduler
80 * (<code>org.quartz.Scheduler</code>). This allows you to create new jobs
81 * and triggers, and also to control and monitor the entire Scheduler.
82 *
83 * <p>Note that Quartz instantiates a new Job for each execution, in
84 * contrast to Timer which uses a TimerTask instance that is shared
85 * between repeated executions. Just JobDetail descriptors are shared.
86 *
87 * <p>When using persistent jobs, it is strongly recommended to perform all
88 * operations on the Scheduler within Spring-managed (or plain JTA) transactions.
89 * Else, database locking will not properly work and might even break.
90 * (See {@link #setDataSource setDataSource} javadoc for details.)
91 *
92 * <p>The preferred way to achieve transactional execution is to demarcate
93 * declarative transactions at the business facade level, which will
94 * automatically apply to Scheduler operations performed within those scopes.
95 * Alternatively, you may add transactional advice for the Scheduler itself.
96 *
97 * <p><b>Note:</b> This version of Spring's SchedulerFactoryBean requires
98 * Quartz 1.5.x or 1.6.x. The "jobSchedulingDataLocation" feature requires
99 * Quartz 1.6.1 or higher (as of Spring 2.5.5).
100 *
101 * @author Juergen Hoeller
102 * @since 18.02.2004
103 * @see #setDataSource
104 * @see org.quartz.Scheduler
105 * @see org.quartz.SchedulerFactory
106 * @see org.quartz.impl.StdSchedulerFactory
107 * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean
108 */
109 public class SchedulerFactoryBean
110 implements FactoryBean, ResourceLoaderAware, ApplicationContextAware, InitializingBean, DisposableBean, Lifecycle {
111
112 public static final String PROP_THREAD_COUNT = "org.quartz.threadPool.threadCount";
113
114 public static final int DEFAULT_THREAD_COUNT = 10;
115
116
117 private static final ThreadLocal configTimeResourceLoaderHolder = new ThreadLocal();
118
119 private static final ThreadLocal configTimeTaskExecutorHolder = new ThreadLocal();
120
121 private static final ThreadLocal configTimeDataSourceHolder = new ThreadLocal();
122
123 private static final ThreadLocal configTimeNonTransactionalDataSourceHolder = new ThreadLocal();
124
125 /**
126 * Return the ResourceLoader for the currently configured Quartz Scheduler,
127 * to be used by ResourceLoaderClassLoadHelper.
128 * <p>This instance will be set before initialization of the corresponding
129 * Scheduler, and reset immediately afterwards. It is thus only available
130 * during configuration.
131 * @see #setApplicationContext
132 * @see ResourceLoaderClassLoadHelper
133 */
134 public static ResourceLoader getConfigTimeResourceLoader() {
135 return (ResourceLoader) configTimeResourceLoaderHolder.get();
136 }
137
138 /**
139 * Return the TaskExecutor for the currently configured Quartz Scheduler,
140 * to be used by LocalTaskExecutorThreadPool.
141 * <p>This instance will be set before initialization of the corresponding
142 * Scheduler, and reset immediately afterwards. It is thus only available
143 * during configuration.
144 * @see #setTaskExecutor
145 * @see LocalTaskExecutorThreadPool
146 */
147 public static TaskExecutor getConfigTimeTaskExecutor() {
148 return (TaskExecutor) configTimeTaskExecutorHolder.get();
149 }
150
151 /**
152 * Return the DataSource for the currently configured Quartz Scheduler,
153 * to be used by LocalDataSourceJobStore.
154 * <p>This instance will be set before initialization of the corresponding
155 * Scheduler, and reset immediately afterwards. It is thus only available
156 * during configuration.
157 * @see #setDataSource
158 * @see LocalDataSourceJobStore
159 */
160 public static DataSource getConfigTimeDataSource() {
161 return (DataSource) configTimeDataSourceHolder.get();
162 }
163
164 /**
165 * Return the non-transactional DataSource for the currently configured
166 * Quartz Scheduler, to be used by LocalDataSourceJobStore.
167 * <p>This instance will be set before initialization of the corresponding
168 * Scheduler, and reset immediately afterwards. It is thus only available
169 * during configuration.
170 * @see #setNonTransactionalDataSource
171 * @see LocalDataSourceJobStore
172 */
173 public static DataSource getConfigTimeNonTransactionalDataSource() {
174 return (DataSource) configTimeNonTransactionalDataSourceHolder.get();
175 }
176
177
178 protected final Log logger = LogFactory.getLog(getClass());
179
180
181 private Class schedulerFactoryClass = StdSchedulerFactory.class;
182
183 private String schedulerName;
184
185 private Resource configLocation;
186
187 private Properties quartzProperties;
188
189
190 private TaskExecutor taskExecutor;
191
192 private DataSource dataSource;
193
194 private DataSource nonTransactionalDataSource;
195
196 private PlatformTransactionManager transactionManager;
197
198
199 private Map schedulerContextMap;
200
201 private ResourceLoader resourceLoader;
202
203 private ApplicationContext applicationContext;
204
205 private String applicationContextSchedulerContextKey;
206
207 private JobFactory jobFactory;
208
209 private boolean jobFactorySet = false;
210
211
212 private boolean overwriteExistingJobs = false;
213
214 private String[] jobSchedulingDataLocations;
215
216 private List jobDetails;
217
218 private Map calendars;
219
220 private List triggers;
221
222
223 private SchedulerListener[] schedulerListeners;
224
225 private JobListener[] globalJobListeners;
226
227 private JobListener[] jobListeners;
228
229 private TriggerListener[] globalTriggerListeners;
230
231 private TriggerListener[] triggerListeners;
232
233
234 private boolean autoStartup = true;
235
236 private int startupDelay = 0;
237
238 private boolean waitForJobsToCompleteOnShutdown = false;
239
240
241 private Scheduler scheduler;
242
243
244 /**
245 * Set the Quartz SchedulerFactory implementation to use.
246 * <p>Default is StdSchedulerFactory, reading in the standard
247 * quartz.properties from quartz.jar. To use custom Quartz
248 * properties, specify "configLocation" or "quartzProperties".
249 * @see org.quartz.impl.StdSchedulerFactory
250 * @see #setConfigLocation
251 * @see #setQuartzProperties
252 */
253 public void setSchedulerFactoryClass(Class schedulerFactoryClass) {
254 if (schedulerFactoryClass == null || !SchedulerFactory.class.isAssignableFrom(schedulerFactoryClass)) {
255 throw new IllegalArgumentException("schedulerFactoryClass must implement [org.quartz.SchedulerFactory]");
256 }
257 this.schedulerFactoryClass = schedulerFactoryClass;
258 }
259
260 /**
261 * Set the name of the Scheduler to fetch from the SchedulerFactory.
262 * If not specified, the default Scheduler will be used.
263 * @see org.quartz.SchedulerFactory#getScheduler(String)
264 * @see org.quartz.SchedulerFactory#getScheduler
265 */
266 public void setSchedulerName(String schedulerName) {
267 this.schedulerName = schedulerName;
268 }
269
270 /**
271 * Set the location of the Quartz properties config file, for example
272 * as classpath resource "classpath:quartz.properties".
273 * <p>Note: Can be omitted when all necessary properties are specified
274 * locally via this bean, or when relying on Quartz' default configuration.
275 * @see #setQuartzProperties
276 */
277 public void setConfigLocation(Resource configLocation) {
278 this.configLocation = configLocation;
279 }
280
281 /**
282 * Set Quartz properties, like "org.quartz.threadPool.class".
283 * <p>Can be used to override values in a Quartz properties config file,
284 * or to specify all necessary properties locally.
285 * @see #setConfigLocation
286 */
287 public void setQuartzProperties(Properties quartzProperties) {
288 this.quartzProperties = quartzProperties;
289 }
290
291
292 /**
293 * Set the Spring TaskExecutor to use as Quartz backend.
294 * Exposed as thread pool through the Quartz SPI.
295 * <p>Can be used to assign a JDK 1.5 ThreadPoolExecutor or a CommonJ
296 * WorkManager as Quartz backend, to avoid Quartz's manual thread creation.
297 * <p>By default, a Quartz SimpleThreadPool will be used, configured through
298 * the corresponding Quartz properties.
299 * @see #setQuartzProperties
300 * @see LocalTaskExecutorThreadPool
301 * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
302 * @see org.springframework.scheduling.commonj.WorkManagerTaskExecutor
303 */
304 public void setTaskExecutor(TaskExecutor taskExecutor) {
305 this.taskExecutor = taskExecutor;
306 }
307
308 /**
309 * Set the default DataSource to be used by the Scheduler. If set,
310 * this will override corresponding settings in Quartz properties.
311 * <p>Note: If this is set, the Quartz settings should not define
312 * a job store "dataSource" to avoid meaningless double configuration.
313 * <p>A Spring-specific subclass of Quartz' JobStoreCMT will be used.
314 * It is therefore strongly recommended to perform all operations on
315 * the Scheduler within Spring-managed (or plain JTA) transactions.
316 * Else, database locking will not properly work and might even break
317 * (e.g. if trying to obtain a lock on Oracle without a transaction).
318 * <p>Supports both transactional and non-transactional DataSource access.
319 * With a non-XA DataSource and local Spring transactions, a single DataSource
320 * argument is sufficient. In case of an XA DataSource and global JTA transactions,
321 * SchedulerFactoryBean's "nonTransactionalDataSource" property should be set,
322 * passing in a non-XA DataSource that will not participate in global transactions.
323 * @see #setNonTransactionalDataSource
324 * @see #setQuartzProperties
325 * @see #setTransactionManager
326 * @see LocalDataSourceJobStore
327 */
328 public void setDataSource(DataSource dataSource) {
329 this.dataSource = dataSource;
330 }
331
332 /**
333 * Set the DataSource to be used by the Scheduler <i>for non-transactional access</i>.
334 * <p>This is only necessary if the default DataSource is an XA DataSource that will
335 * always participate in transactions: A non-XA version of that DataSource should
336 * be specified as "nonTransactionalDataSource" in such a scenario.
337 * <p>This is not relevant with a local DataSource instance and Spring transactions.
338 * Specifying a single default DataSource as "dataSource" is sufficient there.
339 * @see #setDataSource
340 * @see LocalDataSourceJobStore
341 */
342 public void setNonTransactionalDataSource(DataSource nonTransactionalDataSource) {
343 this.nonTransactionalDataSource = nonTransactionalDataSource;
344 }
345
346 /**
347 * Set the transaction manager to be used for registering jobs and triggers
348 * that are defined by this SchedulerFactoryBean. Default is none; setting
349 * this only makes sense when specifying a DataSource for the Scheduler.
350 * @see #setDataSource
351 */
352 public void setTransactionManager(PlatformTransactionManager transactionManager) {
353 this.transactionManager = transactionManager;
354 }
355
356
357 /**
358 * Register objects in the Scheduler context via a given Map.
359 * These objects will be available to any Job that runs in this Scheduler.
360 * <p>Note: When using persistent Jobs whose JobDetail will be kept in the
361 * database, do not put Spring-managed beans or an ApplicationContext
362 * reference into the JobDataMap but rather into the SchedulerContext.
363 * @param schedulerContextAsMap Map with String keys and any objects as
364 * values (for example Spring-managed beans)
365 * @see JobDetailBean#setJobDataAsMap
366 */
367 public void setSchedulerContextAsMap(Map schedulerContextAsMap) {
368 this.schedulerContextMap = schedulerContextAsMap;
369 }
370
371 /**
372 * Set the key of an ApplicationContext reference to expose in the
373 * SchedulerContext, for example "applicationContext". Default is none.
374 * Only applicable when running in a Spring ApplicationContext.
375 * <p>Note: When using persistent Jobs whose JobDetail will be kept in the
376 * database, do not put an ApplicationContext reference into the JobDataMap
377 * but rather into the SchedulerContext.
378 * <p>In case of a QuartzJobBean, the reference will be applied to the Job
379 * instance as bean property. An "applicationContext" attribute will
380 * correspond to a "setApplicationContext" method in that scenario.
381 * <p>Note that BeanFactory callback interfaces like ApplicationContextAware
382 * are not automatically applied to Quartz Job instances, because Quartz
383 * itself is reponsible for the lifecycle of its Jobs.
384 * @see JobDetailBean#setApplicationContextJobDataKey
385 * @see org.springframework.context.ApplicationContext
386 */
387 public void setApplicationContextSchedulerContextKey(String applicationContextSchedulerContextKey) {
388 this.applicationContextSchedulerContextKey = applicationContextSchedulerContextKey;
389 }
390
391 /**
392 * Set the Quartz JobFactory to use for this Scheduler.
393 * <p>Default is Spring's {@link AdaptableJobFactory}, which supports
394 * {@link java.lang.Runnable} objects as well as standard Quartz
395 * {@link org.quartz.Job} instances. Note that this default only applies
396 * to a <i>local</i> Scheduler, not to a RemoteScheduler (where setting
397 * a custom JobFactory is not supported by Quartz).
398 * <p>Specify an instance of Spring's {@link SpringBeanJobFactory} here
399 * (typically as an inner bean definition) to automatically populate a job's
400 * bean properties from the specified job data map and scheduler context.
401 * @see AdaptableJobFactory
402 * @see SpringBeanJobFactory
403 */
404 public void setJobFactory(JobFactory jobFactory) {
405 this.jobFactory = jobFactory;
406 this.jobFactorySet = true;
407 }
408
409
410 /**
411 * Set whether any jobs defined on this SchedulerFactoryBean should overwrite
412 * existing job definitions. Default is "false", to not overwrite already
413 * registered jobs that have been read in from a persistent job store.
414 */
415 public void setOverwriteExistingJobs(boolean overwriteExistingJobs) {
416 this.overwriteExistingJobs = overwriteExistingJobs;
417 }
418
419 /**
420 * Set the location of a Quartz job definition XML file that follows the
421 * "job_scheduling_data_1_5" XSD. Can be specified to automatically
422 * register jobs that are defined in such a file, possibly in addition
423 * to jobs defined directly on this SchedulerFactoryBean.
424 * @see org.quartz.xml.JobSchedulingDataProcessor
425 */
426 public void setJobSchedulingDataLocation(String jobSchedulingDataLocation) {
427 this.jobSchedulingDataLocations = new String[] {jobSchedulingDataLocation};
428 }
429
430 /**
431 * Set the locations of Quartz job definition XML files that follow the
432 * "job_scheduling_data_1_5" XSD. Can be specified to automatically
433 * register jobs that are defined in such files, possibly in addition
434 * to jobs defined directly on this SchedulerFactoryBean.
435 * @see org.quartz.xml.JobSchedulingDataProcessor
436 */
437 public void setJobSchedulingDataLocations(String[] jobSchedulingDataLocations) {
438 this.jobSchedulingDataLocations = jobSchedulingDataLocations;
439 }
440
441 /**
442 * Register a list of JobDetail objects with the Scheduler that
443 * this FactoryBean creates, to be referenced by Triggers.
444 * <p>This is not necessary when a Trigger determines the JobDetail
445 * itself: In this case, the JobDetail will be implicitly registered
446 * in combination with the Trigger.
447 * @see #setTriggers
448 * @see org.quartz.JobDetail
449 * @see JobDetailBean
450 * @see JobDetailAwareTrigger
451 * @see org.quartz.Trigger#setJobName
452 */
453 public void setJobDetails(JobDetail[] jobDetails) {
454 // Use modifiable ArrayList here, to allow for further adding of
455 // JobDetail objects during autodetection of JobDetailAwareTriggers.
456 this.jobDetails = new ArrayList(Arrays.asList(jobDetails));
457 }
458
459 /**
460 * Register a list of Quartz Calendar objects with the Scheduler
461 * that this FactoryBean creates, to be referenced by Triggers.
462 * @param calendars Map with calendar names as keys as Calendar
463 * objects as values
464 * @see org.quartz.Calendar
465 * @see org.quartz.Trigger#setCalendarName
466 */
467 public void setCalendars(Map calendars) {
468 this.calendars = calendars;
469 }
470
471 /**
472 * Register a list of Trigger objects with the Scheduler that
473 * this FactoryBean creates.
474 * <p>If the Trigger determines the corresponding JobDetail itself,
475 * the job will be automatically registered with the Scheduler.
476 * Else, the respective JobDetail needs to be registered via the
477 * "jobDetails" property of this FactoryBean.
478 * @see #setJobDetails
479 * @see org.quartz.JobDetail
480 * @see JobDetailAwareTrigger
481 * @see CronTriggerBean
482 * @see SimpleTriggerBean
483 */
484 public void setTriggers(Trigger[] triggers) {
485 this.triggers = Arrays.asList(triggers);
486 }
487
488
489 /**
490 * Specify Quartz SchedulerListeners to be registered with the Scheduler.
491 */
492 public void setSchedulerListeners(SchedulerListener[] schedulerListeners) {
493 this.schedulerListeners = schedulerListeners;
494 }
495
496 /**
497 * Specify global Quartz JobListeners to be registered with the Scheduler.
498 * Such JobListeners will apply to all Jobs in the Scheduler.
499 */
500 public void setGlobalJobListeners(JobListener[] globalJobListeners) {
501 this.globalJobListeners = globalJobListeners;
502 }
503
504 /**
505 * Specify named Quartz JobListeners to be registered with the Scheduler.
506 * Such JobListeners will only apply to Jobs that explicitly activate
507 * them via their name.
508 * @see org.quartz.JobListener#getName
509 * @see org.quartz.JobDetail#addJobListener
510 * @see JobDetailBean#setJobListenerNames
511 */
512 public void setJobListeners(JobListener[] jobListeners) {
513 this.jobListeners = jobListeners;
514 }
515
516 /**
517 * Specify global Quartz TriggerListeners to be registered with the Scheduler.
518 * Such TriggerListeners will apply to all Triggers in the Scheduler.
519 */
520 public void setGlobalTriggerListeners(TriggerListener[] globalTriggerListeners) {
521 this.globalTriggerListeners = globalTriggerListeners;
522 }
523
524 /**
525 * Specify named Quartz TriggerListeners to be registered with the Scheduler.
526 * Such TriggerListeners will only apply to Triggers that explicitly activate
527 * them via their name.
528 * @see org.quartz.TriggerListener#getName
529 * @see org.quartz.Trigger#addTriggerListener
530 * @see CronTriggerBean#setTriggerListenerNames
531 * @see SimpleTriggerBean#setTriggerListenerNames
532 */
533 public void setTriggerListeners(TriggerListener[] triggerListeners) {
534 this.triggerListeners = triggerListeners;
535 }
536
537
538 /**
539 * Set whether to automatically start the scheduler after initialization.
540 * <p>Default is "true"; set this to "false" to allow for manual startup.
541 */
542 public void setAutoStartup(boolean autoStartup) {
543 this.autoStartup = autoStartup;
544 }
545
546 /**
547 * Set the number of seconds to wait after initialization before
548 * starting the scheduler asynchronously. Default is 0, meaning
549 * immediate synchronous startup on initialization of this bean.
550 * <p>Setting this to 10 or 20 seconds makes sense if no jobs
551 * should be run before the entire application has started up.
552 */
553 public void setStartupDelay(int startupDelay) {
554 this.startupDelay = startupDelay;
555 }
556
557 /**
558 * Set whether to wait for running jobs to complete on shutdown.
559 * <p>Default is "false". Switch this to "true" if you prefer
560 * fully completed jobs at the expense of a longer shutdown phase.
561 * @see org.quartz.Scheduler#shutdown(boolean)
562 */
563 public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
564 this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
565 }
566
567
568 public void setResourceLoader(ResourceLoader resourceLoader) {
569 this.resourceLoader = resourceLoader;
570 }
571
572 public void setApplicationContext(ApplicationContext applicationContext) {
573 this.applicationContext = applicationContext;
574 }
575
576
577 //---------------------------------------------------------------------
578 // Implementation of InitializingBean interface
579 //---------------------------------------------------------------------
580
581 public void afterPropertiesSet() throws Exception {
582 if (this.applicationContext != null && this.resourceLoader == null) {
583 this.resourceLoader = this.applicationContext;
584 }
585
586 if (this.dataSource == null && this.nonTransactionalDataSource != null) {
587 this.dataSource = this.nonTransactionalDataSource;
588 }
589
590 // Create SchedulerFactory instance.
591 SchedulerFactory schedulerFactory = (SchedulerFactory)
592 BeanUtils.instantiateClass(this.schedulerFactoryClass);
593
594 initSchedulerFactory(schedulerFactory);
595
596 if (this.resourceLoader != null) {
597 // Make given ResourceLoader available for SchedulerFactory configuration.
598 configTimeResourceLoaderHolder.set(this.resourceLoader);
599 }
600 if (this.taskExecutor != null) {
601 // Make given TaskExecutor available for SchedulerFactory configuration.
602 configTimeTaskExecutorHolder.set(this.taskExecutor);
603 }
604 if (this.dataSource != null) {
605 // Make given DataSource available for SchedulerFactory configuration.
606 configTimeDataSourceHolder.set(this.dataSource);
607 }
608 if (this.nonTransactionalDataSource != null) {
609 // Make given non-transactional DataSource available for SchedulerFactory configuration.
610 configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource);
611 }
612
613
614 // Get Scheduler instance from SchedulerFactory.
615 try {
616 this.scheduler = createScheduler(schedulerFactory, this.schedulerName);
617 if (!this.jobFactorySet && !(this.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.jobFactory instanceof SchedulerContextAware) {
624 ((SchedulerContextAware) this.jobFactory).setSchedulerContext(this.scheduler.getContext());
625 }
626 this.scheduler.setJobFactory(this.jobFactory);
627 }
628 }
629
630 finally {
631 if (this.resourceLoader != null) {
632 configTimeResourceLoaderHolder.set(null);
633 }
634 if (this.taskExecutor != null) {
635 configTimeTaskExecutorHolder.set(null);
636 }
637 if (this.dataSource != null) {
638 configTimeDataSourceHolder.set(null);
639 }
640 if (this.nonTransactionalDataSource != null) {
641 configTimeNonTransactionalDataSourceHolder.set(null);
642 }
643 }
644
645 populateSchedulerContext();
646
647 registerListeners();
648
649 registerJobsAndTriggers();
650
651 // Start Scheduler immediately, if demanded.
652 if (this.autoStartup) {
653 startScheduler(this.scheduler, this.startupDelay);
654 }
655 }
656
657
658 /**
659 * Load and/or apply Quartz properties to the given SchedulerFactory.
660 * @param schedulerFactory the SchedulerFactory to initialize
661 */
662 private void initSchedulerFactory(SchedulerFactory schedulerFactory)
663 throws SchedulerException, IOException {
664
665 if (!(schedulerFactory instanceof StdSchedulerFactory) &&
666 (this.configLocation != null || this.quartzProperties != null ||
667 this.taskExecutor != null || this.dataSource != null)) {
668 throw new IllegalArgumentException("StdSchedulerFactory required for applying Quartz properties");
669 }
670
671 Properties mergedProps = new Properties();
672
673 if (this.resourceLoader != null) {
674 mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_CLASS_LOAD_HELPER_CLASS,
675 ResourceLoaderClassLoadHelper.class.getName());
676 }
677
678 if (this.taskExecutor != null) {
679 mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS,
680 LocalTaskExecutorThreadPool.class.getName());
681 }
682 else {
683 // Set necessary default properties here, as Quartz will not apply
684 // its default configuration when explicitly given properties.
685 mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName());
686 mergedProps.setProperty(PROP_THREAD_COUNT, Integer.toString(DEFAULT_THREAD_COUNT));
687 }
688
689 if (this.configLocation != null) {
690 if (logger.isInfoEnabled()) {
691 logger.info("Loading Quartz config from [" + this.configLocation + "]");
692 }
693 PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation);
694 }
695
696 CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps);
697
698 if (this.dataSource != null) {
699 mergedProps.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName());
700 }
701
702 // Make sure to set the scheduler name as configured in the Spring configuration.
703 if (this.schedulerName != null) {
704 mergedProps.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName);
705 }
706
707 ((StdSchedulerFactory) schedulerFactory).initialize(mergedProps);
708 }
709
710 /**
711 * Create the Scheduler instance for the given factory and scheduler name.
712 * Called by afterPropertiesSet.
713 * <p>Default implementation invokes SchedulerFactory's <code>getScheduler</code>
714 * method. Can be overridden for custom Scheduler creation.
715 * @param schedulerFactory the factory to create the Scheduler with
716 * @param schedulerName the name of the scheduler to create
717 * @return the Scheduler instance
718 * @throws SchedulerException if thrown by Quartz methods
719 * @see #afterPropertiesSet
720 * @see org.quartz.SchedulerFactory#getScheduler
721 */
722 protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName)
723 throws SchedulerException {
724
725 // Override thread context ClassLoader to work around naive Quartz ClassLoadHelper loading.
726 Thread currentThread = Thread.currentThread();
727 ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
728 boolean overrideClassLoader = (this.resourceLoader != null &&
729 !this.resourceLoader.getClassLoader().equals(threadContextClassLoader));
730 if (overrideClassLoader) {
731 currentThread.setContextClassLoader(this.resourceLoader.getClassLoader());
732 }
733 try {
734 return schedulerFactory.getScheduler();
735 }
736 finally {
737 if (overrideClassLoader) {
738 // Reset original thread context ClassLoader.
739 currentThread.setContextClassLoader(threadContextClassLoader);
740 }
741 }
742 }
743
744 /**
745 * Expose the specified context attributes and/or the current
746 * ApplicationContext in the Quartz SchedulerContext.
747 */
748 private void populateSchedulerContext() throws SchedulerException {
749 // Put specified objects into Scheduler context.
750 if (this.schedulerContextMap != null) {
751 this.scheduler.getContext().putAll(this.schedulerContextMap);
752 }
753
754 // Register ApplicationContext in Scheduler context.
755 if (this.applicationContextSchedulerContextKey != null) {
756 if (this.applicationContext == null) {
757 throw new IllegalStateException(
758 "SchedulerFactoryBean needs to be set up in an ApplicationContext " +
759 "to be able to handle an 'applicationContextSchedulerContextKey'");
760 }
761 this.scheduler.getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext);
762 }
763 }
764
765
766 /**
767 * Register all specified listeners with the Scheduler.
768 */
769 private void registerListeners() throws SchedulerException {
770 if (this.schedulerListeners != null) {
771 for (int i = 0; i < this.schedulerListeners.length; i++) {
772 this.scheduler.addSchedulerListener(this.schedulerListeners[i]);
773 }
774 }
775 if (this.globalJobListeners != null) {
776 for (int i = 0; i < this.globalJobListeners.length; i++) {
777 this.scheduler.addGlobalJobListener(this.globalJobListeners[i]);
778 }
779 }
780 if (this.jobListeners != null) {
781 for (int i = 0; i < this.jobListeners.length; i++) {
782 this.scheduler.addJobListener(this.jobListeners[i]);
783 }
784 }
785 if (this.globalTriggerListeners != null) {
786 for (int i = 0; i < this.globalTriggerListeners.length; i++) {
787 this.scheduler.addGlobalTriggerListener(this.globalTriggerListeners[i]);
788 }
789 }
790 if (this.triggerListeners != null) {
791 for (int i = 0; i < this.triggerListeners.length; i++) {
792 this.scheduler.addTriggerListener(this.triggerListeners[i]);
793 }
794 }
795 }
796
797 /**
798 * Register jobs and triggers (within a transaction, if possible).
799 */
800 private void registerJobsAndTriggers() throws SchedulerException {
801 TransactionStatus transactionStatus = null;
802 if (this.transactionManager != null) {
803 transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
804 }
805 try {
806
807 if (this.jobSchedulingDataLocations != null) {
808 ClassLoadHelper clh = new ResourceLoaderClassLoadHelper(this.resourceLoader);
809 clh.initialize();
810 JobSchedulingDataProcessor dataProcessor = new JobSchedulingDataProcessor(clh, true, true);
811 for (int i = 0; i < this.jobSchedulingDataLocations.length; i++) {
812 dataProcessor.processFileAndScheduleJobs(
813 this.jobSchedulingDataLocations[i], this.scheduler, this.overwriteExistingJobs);
814 }
815 }
816
817 // Register JobDetails.
818 if (this.jobDetails != null) {
819 for (Iterator it = this.jobDetails.iterator(); it.hasNext();) {
820 JobDetail jobDetail = (JobDetail) it.next();
821 addJobToScheduler(jobDetail);
822 }
823 }
824 else {
825 // Create empty list for easier checks when registering triggers.
826 this.jobDetails = new LinkedList();
827 }
828
829 // Register Calendars.
830 if (this.calendars != null) {
831 for (Iterator it = this.calendars.keySet().iterator(); it.hasNext();) {
832 String calendarName = (String) it.next();
833 Calendar calendar = (Calendar) this.calendars.get(calendarName);
834 this.scheduler.addCalendar(calendarName, calendar, true, true);
835 }
836 }
837
838 // Register Triggers.
839 if (this.triggers != null) {
840 for (Iterator it = this.triggers.iterator(); it.hasNext();) {
841 Trigger trigger = (Trigger) it.next();
842 addTriggerToScheduler(trigger);
843 }
844 }
845 }
846
847 catch (Throwable ex) {
848 if (transactionStatus != null) {
849 try {
850 this.transactionManager.rollback(transactionStatus);
851 }
852 catch (TransactionException tex) {
853 logger.error("Job registration exception overridden by rollback exception", ex);
854 throw tex;
855 }
856 }
857 if (ex instanceof SchedulerException) {
858 throw (SchedulerException) ex;
859 }
860 if (ex instanceof Exception) {
861 throw new SchedulerException(
862 "Registration of jobs and triggers failed: " + ex.getMessage(), (Exception) ex);
863 }
864 throw new SchedulerException("Registration of jobs and triggers failed: " + ex.getMessage());
865 }
866
867 if (transactionStatus != null) {
868 this.transactionManager.commit(transactionStatus);
869 }
870 }
871
872 /**
873 * Add the given job to the Scheduler, if it doesn't already exist.
874 * Overwrites the job in any case if "overwriteExistingJobs" is set.
875 * @param jobDetail the job to add
876 * @return <code>true</code> if the job was actually added,
877 * <code>false</code> if it already existed before
878 * @see #setOverwriteExistingJobs
879 */
880 private boolean addJobToScheduler(JobDetail jobDetail) throws SchedulerException {
881 if (this.overwriteExistingJobs ||
882 this.scheduler.getJobDetail(jobDetail.getName(), jobDetail.getGroup()) == null) {
883 this.scheduler.addJob(jobDetail, true);
884 return true;
885 }
886 else {
887 return false;
888 }
889 }
890
891 /**
892 * Add the given trigger to the Scheduler, if it doesn't already exist.
893 * Overwrites the trigger in any case if "overwriteExistingJobs" is set.
894 * @param trigger the trigger to add
895 * @return <code>true</code> if the trigger was actually added,
896 * <code>false</code> if it already existed before
897 * @see #setOverwriteExistingJobs
898 */
899 private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException {
900 boolean triggerExists = (this.scheduler.getTrigger(trigger.getName(), trigger.getGroup()) != null);
901 if (!triggerExists || this.overwriteExistingJobs) {
902 // Check if the Trigger is aware of an associated JobDetail.
903 if (trigger instanceof JobDetailAwareTrigger) {
904 JobDetail jobDetail = ((JobDetailAwareTrigger) trigger).getJobDetail();
905 // Automatically register the JobDetail too.
906 if (!this.jobDetails.contains(jobDetail) && addJobToScheduler(jobDetail)) {
907 this.jobDetails.add(jobDetail);
908 }
909 }
910 if (!triggerExists) {
911 try {
912 this.scheduler.scheduleJob(trigger);
913 }
914 catch (ObjectAlreadyExistsException ex) {
915 if (logger.isDebugEnabled()) {
916 logger.debug("Unexpectedly found existing trigger, assumably due to cluster race condition: " +
917 ex.getMessage() + " - can safely be ignored");
918 }
919 if (this.overwriteExistingJobs) {
920 this.scheduler.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
921 }
922 }
923 }
924 else {
925 this.scheduler.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
926 }
927 return true;
928 }
929 else {
930 return false;
931 }
932 }
933
934
935 /**
936 * Start the Quartz Scheduler, respecting the "startupDelay" setting.
937 * @param scheduler the Scheduler to start
938 * @param startupDelay the number of seconds to wait before starting
939 * the Scheduler asynchronously
940 */
941 protected void startScheduler(final Scheduler scheduler, final int startupDelay) throws SchedulerException {
942 if (startupDelay <= 0) {
943 logger.info("Starting Quartz Scheduler now");
944 scheduler.start();
945 }
946 else {
947 if (logger.isInfoEnabled()) {
948 logger.info("Will start Quartz Scheduler [" + scheduler.getSchedulerName() +
949 "] in " + startupDelay + " seconds");
950 }
951 Thread schedulerThread = new Thread() {
952 public void run() {
953 try {
954 Thread.sleep(startupDelay * 1000);
955 }
956 catch (InterruptedException ex) {
957 // simply proceed
958 }
959 if (logger.isInfoEnabled()) {
960 logger.info("Starting Quartz Scheduler now, after delay of " + startupDelay + " seconds");
961 }
962 try {
963 scheduler.start();
964 }
965 catch (SchedulerException ex) {
966 throw new SchedulingException("Could not start Quartz Scheduler after delay", ex);
967 }
968 }
969 };
970 schedulerThread.setName("Quartz Scheduler [" + scheduler.getSchedulerName() + "]");
971 schedulerThread.start();
972 }
973 }
974
975
976 //---------------------------------------------------------------------
977 // Implementation of FactoryBean interface
978 //---------------------------------------------------------------------
979
980 public Object getObject() {
981 return this.scheduler;
982 }
983
984 public Class getObjectType() {
985 return (this.scheduler != null) ? this.scheduler.getClass() : Scheduler.class;
986 }
987
988 public boolean isSingleton() {
989 return true;
990 }
991
992
993 //---------------------------------------------------------------------
994 // Implementation of Lifecycle interface
995 //---------------------------------------------------------------------
996
997 public void start() throws SchedulingException {
998 if (this.scheduler != null) {
999 try {
1000 this.scheduler.start();
1001 }
1002 catch (SchedulerException ex) {
1003 throw new SchedulingException("Could not start Quartz Scheduler", ex);
1004 }
1005 }
1006 }
1007
1008 public void stop() throws SchedulingException {
1009 if (this.scheduler != null) {
1010 try {
1011 this.scheduler.standby();
1012 }
1013 catch (SchedulerException ex) {
1014 throw new SchedulingException("Could not stop Quartz Scheduler", ex);
1015 }
1016 }
1017 }
1018
1019 public boolean isRunning() throws SchedulingException {
1020 if (this.scheduler != null) {
1021 try {
1022 return !this.scheduler.isInStandbyMode();
1023 }
1024 catch (SchedulerException ex) {
1025 return false;
1026 }
1027 }
1028 return false;
1029 }
1030
1031
1032 //---------------------------------------------------------------------
1033 // Implementation of DisposableBean interface
1034 //---------------------------------------------------------------------
1035
1036 /**
1037 * Shut down the Quartz scheduler on bean factory shutdown,
1038 * stopping all scheduled jobs.
1039 */
1040 public void destroy() throws SchedulerException {
1041 logger.info("Shutting down Quartz Scheduler");
1042 this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown);
1043 }
1044
1045 }