KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > scheduling > quartz > SchedulerFactoryBean


1 /*
2  * Copyright 2002-2006 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 JavaDoc;
20 import java.util.ArrayList JavaDoc;
21 import java.util.Arrays JavaDoc;
22 import java.util.Iterator JavaDoc;
23 import java.util.LinkedList JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.Map JavaDoc;
26 import java.util.Properties JavaDoc;
27
28 import javax.sql.DataSource JavaDoc;
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.StdSchedulerFactory;
43 import org.quartz.simpl.SimpleThreadPool;
44 import org.quartz.spi.JobFactory;
45
46 import org.springframework.beans.BeanUtils;
47 import org.springframework.beans.factory.DisposableBean;
48 import org.springframework.beans.factory.FactoryBean;
49 import org.springframework.beans.factory.InitializingBean;
50 import org.springframework.context.ApplicationContext;
51 import org.springframework.context.ApplicationContextAware;
52 import org.springframework.context.Lifecycle;
53 import org.springframework.core.io.Resource;
54 import org.springframework.core.io.support.PropertiesLoaderUtils;
55 import org.springframework.core.task.TaskExecutor;
56 import org.springframework.scheduling.SchedulingException;
57 import org.springframework.transaction.PlatformTransactionManager;
58 import org.springframework.transaction.TransactionException;
59 import org.springframework.transaction.TransactionStatus;
60 import org.springframework.transaction.support.DefaultTransactionDefinition;
61 import org.springframework.util.CollectionUtils;
62
63 /**
64  * FactoryBean that sets up a Quartz Scheduler and exposes it for bean references.
65  *
66  * <p>Allows registration of JobDetails, Calendars and Triggers, automatically
67  * starting the scheduler on initialization and shutting it down on destruction.
68  * In scenarios that just require static registration of jobs at startup, there
69  * is no need to access the Scheduler instance itself in application code.
70  *
71  * <p>For dynamic registration of jobs at runtime, use a bean reference to
72  * this SchedulerFactoryBean to get direct access to the Quartz Scheduler
73  * (<code>org.quartz.Scheduler</code>). This allows you to create new jobs
74  * and triggers, and also to control and monitor the entire Scheduler.
75  *
76  * <p>Note that Quartz instantiates a new Job for each execution, in
77  * contrast to Timer which uses a TimerTask instance that is shared
78  * between repeated executions. Just JobDetail descriptors are shared.
79  *
80  * <p>When using persistent jobs, it is strongly recommended to perform all
81  * operations on the Scheduler within Spring-managed (or plain JTA) transactions.
82  * Else, database locking will not properly work and might even break.
83  * (See {@link #setDataSource setDataSource} javadoc for details.)
84  *
85  * <p>The preferred way to achieve transactional execution is to demarcate
86  * declarative transactions at the business facade level, which will
87  * automatically apply to Scheduler operations performed within those scopes.
88  * Alternatively, define a TransactionProxyFactoryBean for the Scheduler itself.
89  *
90  * <p>This version of SchedulerFactoryBean requires Quartz 1.5 or higher.
91  *
92  * @author Juergen Hoeller
93  * @since 18.02.2004
94  * @see #setDataSource
95  * @see org.quartz.Scheduler
96  * @see org.quartz.SchedulerFactory
97  * @see org.quartz.impl.StdSchedulerFactory
98  * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean
99  */

100 public class SchedulerFactoryBean
101     implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean, Lifecycle {
102
103     public static final String JavaDoc PROP_THREAD_COUNT = "org.quartz.threadPool.threadCount";
104
105     public static final int DEFAULT_THREAD_COUNT = 10;
106
107
108     private static final ThreadLocal JavaDoc configTimeTaskExecutorHolder = new ThreadLocal JavaDoc();
109
110     private static final ThreadLocal JavaDoc configTimeDataSourceHolder = new ThreadLocal JavaDoc();
111
112     private static final ThreadLocal JavaDoc configTimeNonTransactionalDataSourceHolder = new ThreadLocal JavaDoc();
113
114     /**
115      * Return the TaskExecutor for the currently configured Quartz Scheduler,
116      * to be used by LocalTaskExecutorThreadPool.
117      * <p>This instance will be set before initialization of the corresponding
118      * Scheduler, and reset immediately afterwards. It is thus only available
119      * during configuration.
120      * @see #setDataSource
121      * @see LocalDataSourceJobStore
122      */

123     public static TaskExecutor getConfigTimeTaskExecutor() {
124         return (TaskExecutor) configTimeTaskExecutorHolder.get();
125     }
126
127     /**
128      * Return the DataSource for the currently configured Quartz Scheduler,
129      * to be used by LocalDataSourceJobStore.
130      * <p>This instance will be set before initialization of the corresponding
131      * Scheduler, and reset immediately afterwards. It is thus only available
132      * during configuration.
133      * @see #setDataSource
134      * @see LocalDataSourceJobStore
135      */

136     public static DataSource JavaDoc getConfigTimeDataSource() {
137         return (DataSource JavaDoc) configTimeDataSourceHolder.get();
138     }
139
140     /**
141      * Return the non-transactional DataSource for the currently configured
142      * Quartz Scheduler, to be used by LocalDataSourceJobStore.
143      * <p>This instance will be set before initialization of the corresponding
144      * Scheduler, and reset immediately afterwards. It is thus only available
145      * during configuration.
146      * @see #setNonTransactionalDataSource
147      * @see LocalDataSourceJobStore
148      */

149     public static DataSource JavaDoc getConfigTimeNonTransactionalDataSource() {
150         return (DataSource JavaDoc) configTimeNonTransactionalDataSourceHolder.get();
151     }
152
153
154     protected final Log logger = LogFactory.getLog(getClass());
155
156
157     private Class JavaDoc schedulerFactoryClass = StdSchedulerFactory.class;
158
159     private String JavaDoc schedulerName;
160
161     private Resource configLocation;
162
163     private Properties JavaDoc quartzProperties;
164
165
166     private TaskExecutor taskExecutor;
167
168     private DataSource JavaDoc dataSource;
169
170     private DataSource JavaDoc nonTransactionalDataSource;
171
172     private PlatformTransactionManager transactionManager;
173
174
175     private Map JavaDoc schedulerContextMap;
176
177     private ApplicationContext applicationContext;
178
179     private String JavaDoc applicationContextSchedulerContextKey;
180
181     private JobFactory jobFactory = new AdaptableJobFactory();
182
183
184     private boolean overwriteExistingJobs = false;
185
186     private String JavaDoc[] jobSchedulingDataLocations;
187
188     private List JavaDoc jobDetails;
189
190     private Map JavaDoc calendars;
191
192     private List JavaDoc triggers;
193
194
195     private SchedulerListener[] schedulerListeners;
196
197     private JobListener[] globalJobListeners;
198
199     private JobListener[] jobListeners;
200
201     private TriggerListener[] globalTriggerListeners;
202
203     private TriggerListener[] triggerListeners;
204
205
206     private boolean autoStartup = true;
207
208     private int startupDelay = 0;
209
210     private boolean waitForJobsToCompleteOnShutdown = false;
211
212
213     private Scheduler scheduler;
214
215
216     /**
217      * Set the Quartz SchedulerFactory implementation to use.
218      * <p>Default is StdSchedulerFactory, reading in the standard
219      * quartz.properties from quartz.jar. To use custom Quartz
220      * properties, specify "configLocation" or "quartzProperties".
221      * @see org.quartz.impl.StdSchedulerFactory
222      * @see #setConfigLocation
223      * @see #setQuartzProperties
224      */

225     public void setSchedulerFactoryClass(Class JavaDoc schedulerFactoryClass) {
226         if (schedulerFactoryClass == null || !SchedulerFactory.class.isAssignableFrom(schedulerFactoryClass)) {
227             throw new IllegalArgumentException JavaDoc("schedulerFactoryClass must implement [org.quartz.SchedulerFactory]");
228         }
229         this.schedulerFactoryClass = schedulerFactoryClass;
230     }
231
232     /**
233      * Set the name of the Scheduler to fetch from the SchedulerFactory.
234      * If not specified, the default Scheduler will be used.
235      * @see org.quartz.SchedulerFactory#getScheduler(String)
236      * @see org.quartz.SchedulerFactory#getScheduler
237      */

238     public void setSchedulerName(String JavaDoc schedulerName) {
239         this.schedulerName = schedulerName;
240     }
241
242     /**
243      * Set the location of the Quartz properties config file, for example
244      * as classpath resource "classpath:quartz.properties".
245      * <p>Note: Can be omitted when all necessary properties are specified
246      * locally via this bean, or when relying on Quartz' default configuration.
247      * @see #setQuartzProperties
248      */

249     public void setConfigLocation(Resource configLocation) {
250         this.configLocation = configLocation;
251     }
252
253     /**
254      * Set Quartz properties, like "org.quartz.threadPool.class".
255      * <p>Can be used to override values in a Quartz properties config file,
256      * or to specify all necessary properties locally.
257      * @see #setConfigLocation
258      */

259     public void setQuartzProperties(Properties JavaDoc quartzProperties) {
260         this.quartzProperties = quartzProperties;
261     }
262
263
264     /**
265      * Set the Spring TaskExecutor to use as Quartz backend.
266      * Exposed as thread pool through the Quartz SPI.
267      * <p>Can be used to assign a JDK 1.5 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      * @see #setQuartzProperties
272      * @see LocalTaskExecutorThreadPool
273      * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
274      * @see org.springframework.scheduling.commonj.WorkManagerTaskExecutor
275      */

276     public void setTaskExecutor(TaskExecutor taskExecutor) {
277         this.taskExecutor = taskExecutor;
278     }
279
280     /**
281      * Set the default DataSource to be used by the Scheduler. If set,
282      * this will override corresponding settings in Quartz properties.
283      * <p>Note: If this is set, the Quartz settings should not define
284      * a job store "dataSource" to avoid meaningless double configuration.
285      * <p>A Spring-specific subclass of Quartz' JobStoreCMT will be used.
286      * It is therefore strongly recommended to perform all operations on
287      * the Scheduler within Spring-managed (or plain JTA) transactions.
288      * Else, database locking will not properly work and might even break
289      * (e.g. if trying to obtain a lock on Oracle without a transaction).
290      * <p>Supports both transactional and non-transactional DataSource access.
291      * With a non-XA DataSource and local Spring transactions, a single DataSource
292      * argument is sufficient. In case of an XA DataSource and global JTA transactions,
293      * SchedulerFactoryBean's "nonTransactionalDataSource" property should be set,
294      * passing in a non-XA DataSource that will not participate in global transactions.
295      * @see #setNonTransactionalDataSource
296      * @see #setQuartzProperties
297      * @see #setTransactionManager
298      * @see LocalDataSourceJobStore
299      */

300     public void setDataSource(DataSource JavaDoc dataSource) {
301         this.dataSource = dataSource;
302     }
303
304     /**
305      * Set the DataSource to be used by the Scheduler <i>for non-transactional access</i>.
306      * <p>This is only necessary if the default DataSource is an XA DataSource that will
307      * always participate in transactions: A non-XA version of that DataSource should
308      * be specified as "nonTransactionalDataSource" in such a scenario.
309      * <p>This is not relevant with a local DataSource instance and Spring transactions.
310      * Specifying a single default DataSource as "dataSource" is sufficient there.
311      * @see #setDataSource
312      * @see org.springframework.scheduling.quartz.LocalDataSourceJobStore
313      */

314     public void setNonTransactionalDataSource(DataSource JavaDoc nonTransactionalDataSource) {
315         this.nonTransactionalDataSource = nonTransactionalDataSource;
316     }
317
318     /**
319      * Set the transaction manager to be used for registering jobs and triggers
320      * that are defined by this SchedulerFactoryBean. Default is none; setting
321      * this only makes sense when specifying a DataSource for the Scheduler.
322      * @see #setDataSource
323      */

324     public void setTransactionManager(PlatformTransactionManager transactionManager) {
325         this.transactionManager = transactionManager;
326     }
327
328
329     /**
330      * Register objects in the Scheduler context via a given Map.
331      * These objects will be available to any Job that runs in this Scheduler.
332      * <p>Note: When using persistent Jobs whose JobDetail will be kept in the
333      * database, do not put Spring-managed beans or an ApplicationContext
334      * reference into the JobDataMap but rather into the SchedulerContext.
335      * @param schedulerContextAsMap Map with String keys and any objects as
336      * values (for example Spring-managed beans)
337      * @see JobDetailBean#setJobDataAsMap
338      */

339     public void setSchedulerContextAsMap(Map JavaDoc schedulerContextAsMap) {
340         this.schedulerContextMap = schedulerContextAsMap;
341     }
342
343     public void setApplicationContext(ApplicationContext applicationContext) {
344         this.applicationContext = applicationContext;
345     }
346
347     /**
348      * Set the key of an ApplicationContext reference to expose in the
349      * SchedulerContext, for example "applicationContext". Default is none.
350      * Only applicable when running in a Spring ApplicationContext.
351      * <p>Note: When using persistent Jobs whose JobDetail will be kept in the
352      * database, do not put an ApplicationContext reference into the JobDataMap
353      * but rather into the SchedulerContext.
354      * <p>In case of a QuartzJobBean, the reference will be applied to the Job
355      * instance as bean property. An "applicationContext" attribute will
356      * correspond to a "setApplicationContext" method in that scenario.
357      * <p>Note that BeanFactory callback interfaces like ApplicationContextAware
358      * are not automatically applied to Quartz Job instances, because Quartz
359      * itself is reponsible for the lifecycle of its Jobs.
360      * @see JobDetailBean#setApplicationContextJobDataKey
361      * @see org.springframework.context.ApplicationContext
362      */

363     public void setApplicationContextSchedulerContextKey(String JavaDoc applicationContextSchedulerContextKey) {
364         this.applicationContextSchedulerContextKey = applicationContextSchedulerContextKey;
365     }
366
367     /**
368      * Set the Quartz JobFactory to use for this Scheduler.
369      * <p>Default is Spring's {@link AdaptableJobFactory}, which supports
370      * {@link java.lang.Runnable} objects as well as standard Quartz
371      * {@link org.quartz.Job} instances.
372      * <p>Specify an instance of Spring's {@link SpringBeanJobFactory} here
373      * (typically as an inner bean definition) to automatically populate a
374      * job's bean properties from the specified job data map and scheduler
375      * context.
376      * @see AdaptableJobFactory
377      * @see SpringBeanJobFactory
378      */

379     public void setJobFactory(JobFactory jobFactory) {
380         this.jobFactory = jobFactory;
381     }
382
383
384     /**
385      * Set whether any jobs defined on this SchedulerFactoryBean should overwrite
386      * existing job definitions. Default is "false", to not overwrite already
387      * registered jobs that have been read in from a persistent job store.
388      */

389     public void setOverwriteExistingJobs(boolean overwriteExistingJobs) {
390         this.overwriteExistingJobs = overwriteExistingJobs;
391     }
392
393     /**
394      * Set the location of a Quartz job definition XML file that follows the
395      * "job_scheduling_data_1_0" DTD. Can be specified to automatically
396      * register jobs that are defined in such a file, possibly in addition
397      * to jobs defined directly on this SchedulerFactoryBean.
398      * @see ResourceJobSchedulingDataProcessor
399      * @see org.quartz.xml.JobSchedulingDataProcessor
400      */

401     public void setJobSchedulingDataLocation(String JavaDoc jobSchedulingDataLocation) {
402         this.jobSchedulingDataLocations = new String JavaDoc[] {jobSchedulingDataLocation};
403     }
404
405     /**
406      * Set the locations of Quartz job definition XML files that follow the
407      * "job_scheduling_data_1_0" DTD. Can be specified to automatically
408      * register jobs that are defined in such files, possibly in addition
409      * to jobs defined directly on this SchedulerFactoryBean.
410      * @see ResourceJobSchedulingDataProcessor
411      * @see org.quartz.xml.JobSchedulingDataProcessor
412      */

413     public void setJobSchedulingDataLocations(String JavaDoc[] jobSchedulingDataLocations) {
414         this.jobSchedulingDataLocations = jobSchedulingDataLocations;
415     }
416
417     /**
418      * Register a list of JobDetail objects with the Scheduler that
419      * this FactoryBean creates, to be referenced by Triggers.
420      * <p>This is not necessary when a Trigger determines the JobDetail
421      * itself: In this case, the JobDetail will be implicitly registered
422      * in combination with the Trigger.
423      * @see #setTriggers
424      * @see org.quartz.JobDetail
425      * @see JobDetailBean
426      * @see JobDetailAwareTrigger
427      * @see org.quartz.Trigger#setJobName
428      */

429     public void setJobDetails(JobDetail[] jobDetails) {
430         // Use modifiable ArrayList here, to allow for further adding of
431
// JobDetail objects during autodetection of JobDetailAwareTriggers.
432
this.jobDetails = new ArrayList JavaDoc(Arrays.asList(jobDetails));
433     }
434
435     /**
436      * Register a list of Quartz Calendar objects with the Scheduler
437      * that this FactoryBean creates, to be referenced by Triggers.
438      * @param calendars Map with calendar names as keys as Calendar
439      * objects as values
440      * @see org.quartz.Calendar
441      * @see org.quartz.Trigger#setCalendarName
442      */

443     public void setCalendars(Map JavaDoc calendars) {
444         this.calendars = calendars;
445     }
446
447     /**
448      * Register a list of Trigger objects with the Scheduler that
449      * this FactoryBean creates.
450      * <p>If the Trigger determines the corresponding JobDetail itself,
451      * the job will be automatically registered with the Scheduler.
452      * Else, the respective JobDetail needs to be registered via the
453      * "jobDetails" property of this FactoryBean.
454      * @see #setJobDetails
455      * @see org.quartz.JobDetail
456      * @see JobDetailAwareTrigger
457      * @see CronTriggerBean
458      * @see SimpleTriggerBean
459      */

460     public void setTriggers(Trigger[] triggers) {
461         this.triggers = Arrays.asList(triggers);
462     }
463
464
465     /**
466      * Specify Quartz SchedulerListeners to be registered with the Scheduler.
467      */

468     public void setSchedulerListeners(SchedulerListener[] schedulerListeners) {
469         this.schedulerListeners = schedulerListeners;
470     }
471
472     /**
473      * Specify global Quartz JobListeners to be registered with the Scheduler.
474      * Such JobListeners will apply to all Jobs in the Scheduler.
475      */

476     public void setGlobalJobListeners(JobListener[] globalJobListeners) {
477         this.globalJobListeners = globalJobListeners;
478     }
479
480     /**
481      * Specify named Quartz JobListeners to be registered with the Scheduler.
482      * Such JobListeners will only apply to Jobs that explicitly activate
483      * them via their name.
484      * @see org.quartz.JobListener#getName
485      * @see org.quartz.JobDetail#addJobListener
486      * @see JobDetailBean#setJobListenerNames
487      */

488     public void setJobListeners(JobListener[] jobListeners) {
489         this.jobListeners = jobListeners;
490     }
491
492     /**
493      * Specify global Quartz TriggerListeners to be registered with the Scheduler.
494      * Such TriggerListeners will apply to all Triggers in the Scheduler.
495      */

496     public void setGlobalTriggerListeners(TriggerListener[] globalTriggerListeners) {
497         this.globalTriggerListeners = globalTriggerListeners;
498     }
499
500     /**
501      * Specify named Quartz TriggerListeners to be registered with the Scheduler.
502      * Such TriggerListeners will only apply to Triggers that explicitly activate
503      * them via their name.
504      * @see org.quartz.TriggerListener#getName
505      * @see org.quartz.Trigger#addTriggerListener
506      * @see CronTriggerBean#setTriggerListenerNames
507      * @see SimpleTriggerBean#setTriggerListenerNames
508      */

509     public void setTriggerListeners(TriggerListener[] triggerListeners) {
510         this.triggerListeners = triggerListeners;
511     }
512
513
514     /**
515      * Set whether to automatically start the scheduler after initialization.
516      * Default is "true"; set this to "false" to allow for manual startup.
517      */

518     public void setAutoStartup(boolean autoStartup) {
519         this.autoStartup = autoStartup;
520     }
521
522     /**
523      * Set the number of seconds to wait after initialization before
524      * starting the scheduler asynchronously. Default is 0, meaning
525      * immediate synchronous startup on initialization of this bean.
526      * <p>Setting this to 10 or 20 seconds makes sense if no jobs
527      * should be run before the entire application has started up.
528      */

529     public void setStartupDelay(int startupDelay) {
530         this.startupDelay = startupDelay;
531     }
532
533     /**
534      * Set whether to wait for running jobs to complete on shutdown.
535      * Default is "false".
536      * @see org.quartz.Scheduler#shutdown(boolean)
537      */

538     public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
539         this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
540     }
541
542
543     //---------------------------------------------------------------------
544
// Implementation of InitializingBean interface
545
//---------------------------------------------------------------------
546

547     public void afterPropertiesSet() throws Exception JavaDoc {
548         if (this.dataSource == null && this.nonTransactionalDataSource != null) {
549             this.dataSource = this.nonTransactionalDataSource;
550         }
551
552         // Create SchedulerFactory instance.
553
SchedulerFactory schedulerFactory = (SchedulerFactory)
554                 BeanUtils.instantiateClass(this.schedulerFactoryClass);
555
556         initSchedulerFactory(schedulerFactory);
557
558         if (this.taskExecutor != null) {
559             // Make given TaskExecutor available for SchedulerFactory configuration.
560
configTimeTaskExecutorHolder.set(this.taskExecutor);
561         }
562         if (this.dataSource != null) {
563             // Make given DataSource available for SchedulerFactory configuration.
564
configTimeDataSourceHolder.set(this.dataSource);
565         }
566         if (this.nonTransactionalDataSource != null) {
567             // Make given non-transactional DataSource available for SchedulerFactory configuration.
568
configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource);
569         }
570
571
572         // Get Scheduler instance from SchedulerFactory.
573
try {
574             this.scheduler = createScheduler(schedulerFactory, this.schedulerName);
575             if (this.jobFactory != null) {
576                 if (this.jobFactory instanceof SchedulerContextAware) {
577                     ((SchedulerContextAware) this.jobFactory).setSchedulerContext(this.scheduler.getContext());
578                 }
579                 this.scheduler.setJobFactory(this.jobFactory);
580             }
581         }
582
583         finally {
584             if (this.taskExecutor != null) {
585                 configTimeTaskExecutorHolder.set(null);
586             }
587             if (this.dataSource != null) {
588                 configTimeDataSourceHolder.set(null);
589             }
590             if (this.nonTransactionalDataSource != null) {
591                 configTimeNonTransactionalDataSourceHolder.set(null);
592             }
593         }
594
595         populateSchedulerContext();
596
597         registerListeners();
598
599         registerJobsAndTriggers();
600
601         // Start Scheduler immediately, if demanded.
602
if (this.autoStartup) {
603             startScheduler(this.scheduler, this.startupDelay);
604         }
605     }
606
607
608     /**
609      * Load and/or apply Quartz properties to the given SchedulerFactory.
610      * @param schedulerFactory the SchedulerFactory to initialize
611      */

612     private void initSchedulerFactory(SchedulerFactory schedulerFactory)
613             throws SchedulerException, IOException JavaDoc {
614
615         if (this.configLocation != null || this.quartzProperties != null ||
616                 this.dataSource != null || this.schedulerName != null || this.taskExecutor != null) {
617
618             if (!(schedulerFactory instanceof StdSchedulerFactory)) {
619                 throw new IllegalArgumentException JavaDoc("StdSchedulerFactory required for applying Quartz properties");
620             }
621
622             Properties JavaDoc mergedProps = new Properties JavaDoc();
623
624             // Set necessary default properties here, as Quartz will not apply
625
// its default configuration when explicitly given properties.
626
if (this.taskExecutor != null) {
627                 mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, LocalTaskExecutorThreadPool.class.getName());
628             }
629             else {
630                 mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName());
631                 mergedProps.setProperty(PROP_THREAD_COUNT, Integer.toString(DEFAULT_THREAD_COUNT));
632             }
633
634             if (this.configLocation != null) {
635                 if (logger.isInfoEnabled()) {
636                     logger.info("Loading Quartz config from [" + this.configLocation + "]");
637                 }
638                 PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation);
639             }
640
641             CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps);
642
643             if (this.dataSource != null) {
644                 mergedProps.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName());
645             }
646
647             // Make sure to set the scheduler name as configured in the Spring configuration.
648
if (this.schedulerName != null) {
649                 mergedProps.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName);
650             }
651
652             ((StdSchedulerFactory) schedulerFactory).initialize(mergedProps);
653         }
654     }
655
656     /**
657      * Create the Scheduler instance for the given factory and scheduler name.
658      * Called by afterPropertiesSet.
659      * <p>Default implementation invokes SchedulerFactory's <code>getScheduler</code>
660      * method. Can be overridden for custom Scheduler creation.
661      * @param schedulerFactory the factory to create the Scheduler with
662      * @param schedulerName the name of the scheduler to create
663      * @return the Scheduler instance
664      * @throws SchedulerException if thrown by Quartz methods
665      * @see #afterPropertiesSet
666      * @see org.quartz.SchedulerFactory#getScheduler
667      */

668     protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String JavaDoc schedulerName)
669             throws SchedulerException {
670
671         // StdSchedulerFactory's default "getScheduler" implementation
672
// uses the scheduler name specified in the Quartz properties,
673
// which we have set before (in "initSchedulerFactory").
674
return schedulerFactory.getScheduler();
675     }
676
677     /**
678      * Expose the specified context attributes and/or the current
679      * ApplicationContext in the Quartz SchedulerContext.
680      */

681     private void populateSchedulerContext() throws SchedulerException {
682         // Put specified objects into Scheduler context.
683
if (this.schedulerContextMap != null) {
684             this.scheduler.getContext().putAll(this.schedulerContextMap);
685         }
686
687         // Register ApplicationContext in Scheduler context.
688
if (this.applicationContextSchedulerContextKey != null) {
689             if (this.applicationContext == null) {
690                 throw new IllegalStateException JavaDoc(
691                     "SchedulerFactoryBean needs to be set up in an ApplicationContext " +
692                     "to be able to handle an 'applicationContextSchedulerContextKey'");
693             }
694             this.scheduler.getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext);
695         }
696     }
697
698
699     /**
700      * Register all specified listeners with the Scheduler.
701      */

702     private void registerListeners() throws SchedulerException {
703         if (this.schedulerListeners != null) {
704             for (int i = 0; i < this.schedulerListeners.length; i++) {
705                 this.scheduler.addSchedulerListener(this.schedulerListeners[i]);
706             }
707         }
708         if (this.globalJobListeners != null) {
709             for (int i = 0; i < this.globalJobListeners.length; i++) {
710                 this.scheduler.addGlobalJobListener(this.globalJobListeners[i]);
711             }
712         }
713         if (this.jobListeners != null) {
714             for (int i = 0; i < this.jobListeners.length; i++) {
715                 this.scheduler.addJobListener(this.jobListeners[i]);
716             }
717         }
718         if (this.globalTriggerListeners != null) {
719             for (int i = 0; i < this.globalTriggerListeners.length; i++) {
720                 this.scheduler.addGlobalTriggerListener(this.globalTriggerListeners[i]);
721             }
722         }
723         if (this.triggerListeners != null) {
724             for (int i = 0; i < this.triggerListeners.length; i++) {
725                 this.scheduler.addTriggerListener(this.triggerListeners[i]);
726             }
727         }
728     }
729
730     /**
731      * Register jobs and triggers (within a transaction, if possible).
732      */

733     private void registerJobsAndTriggers() throws SchedulerException {
734         TransactionStatus transactionStatus = null;
735         if (this.transactionManager != null) {
736             transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
737         }
738         try {
739
740             if (this.jobSchedulingDataLocations != null) {
741                 ResourceJobSchedulingDataProcessor dataProcessor = new ResourceJobSchedulingDataProcessor();
742                 if (this.applicationContext != null) {
743                     dataProcessor.setResourceLoader(this.applicationContext);
744                 }
745                 for (int i = 0; i < this.jobSchedulingDataLocations.length; i++) {
746                     dataProcessor.processFileAndScheduleJobs(
747                         this.jobSchedulingDataLocations[i], this.scheduler, this.overwriteExistingJobs);
748                 }
749             }
750
751             // Register JobDetails.
752
if (this.jobDetails != null) {
753                 for (Iterator JavaDoc it = this.jobDetails.iterator(); it.hasNext();) {
754                     JobDetail jobDetail = (JobDetail) it.next();
755                     addJobToScheduler(jobDetail);
756                 }
757             }
758             else {
759                 // Create empty list for easier checks when registering triggers.
760
this.jobDetails = new LinkedList JavaDoc();
761             }
762
763             // Register Calendars.
764
if (this.calendars != null) {
765                 for (Iterator JavaDoc it = this.calendars.keySet().iterator(); it.hasNext();) {
766                     String JavaDoc calendarName = (String JavaDoc) it.next();
767                     Calendar calendar = (Calendar) this.calendars.get(calendarName);
768                     this.scheduler.addCalendar(calendarName, calendar, true, true);
769                 }
770             }
771
772             // Register Triggers.
773
if (this.triggers != null) {
774                 for (Iterator JavaDoc it = this.triggers.iterator(); it.hasNext();) {
775                     Trigger trigger = (Trigger) it.next();
776                     addTriggerToScheduler(trigger);
777                 }
778             }
779         }
780
781         catch (Throwable JavaDoc ex) {
782             if (transactionStatus != null) {
783                 try {
784                     this.transactionManager.rollback(transactionStatus);
785                 }
786                 catch (TransactionException tex) {
787                     logger.error("Job registration exception overridden by rollback exception", ex);
788                     throw tex;
789                 }
790             }
791             if (ex instanceof SchedulerException) {
792                 throw (SchedulerException) ex;
793             }
794             if (ex instanceof Exception JavaDoc) {
795                 throw new SchedulerException(
796                         "Registration of jobs and triggers failed: " + ex.getMessage(), (Exception JavaDoc) ex);
797             }
798             throw new SchedulerException("Registration of jobs and triggers failed: " + ex.getMessage());
799         }
800
801         if (transactionStatus != null) {
802             this.transactionManager.commit(transactionStatus);
803         }
804     }
805
806     /**
807      * Add the given job to the Scheduler, if it doesn't already exist.
808      * Overwrites the job in any case if "overwriteExistingJobs" is set.
809      * @param jobDetail the job to add
810      * @return <code>true</code> if the job was actually added,
811      * <code>false</code> if it already existed before
812      * @see #setOverwriteExistingJobs
813      */

814     private boolean addJobToScheduler(JobDetail jobDetail) throws SchedulerException {
815         if (this.overwriteExistingJobs ||
816             this.scheduler.getJobDetail(jobDetail.getName(), jobDetail.getGroup()) == null) {
817             this.scheduler.addJob(jobDetail, true);
818             return true;
819         }
820         else {
821             return false;
822         }
823     }
824
825     /**
826      * Add the given trigger to the Scheduler, if it doesn't already exist.
827      * Overwrites the trigger in any case if "overwriteExistingJobs" is set.
828      * @param trigger the trigger to add
829      * @return <code>true</code> if the trigger was actually added,
830      * <code>false</code> if it already existed before
831      * @see #setOverwriteExistingJobs
832      */

833     private boolean addTriggerToScheduler(Trigger trigger) throws SchedulerException {
834         boolean triggerExists = (this.scheduler.getTrigger(trigger.getName(), trigger.getGroup()) != null);
835         if (!triggerExists || this.overwriteExistingJobs) {
836             // Check if the Trigger is aware of an associated JobDetail.
837
if (trigger instanceof JobDetailAwareTrigger) {
838                 JobDetail jobDetail = ((JobDetailAwareTrigger) trigger).getJobDetail();
839                 // Automatically register the JobDetail too.
840
if (!this.jobDetails.contains(jobDetail) && addJobToScheduler(jobDetail)) {
841                     this.jobDetails.add(jobDetail);
842                 }
843             }
844             if (!triggerExists) {
845                 try {
846                     this.scheduler.scheduleJob(trigger);
847                 }
848                 catch (ObjectAlreadyExistsException ex) {
849                     if (logger.isDebugEnabled()) {
850                         logger.debug("Unexpectedly found existing trigger, assumably due to cluster race condition: " +
851                                 ex.getMessage() + " - can safely be ignored");
852                     }
853                     if (this.overwriteExistingJobs) {
854                         this.scheduler.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
855                     }
856                 }
857             }
858             else {
859                 this.scheduler.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
860             }
861             return true;
862         }
863         else {
864             return false;
865         }
866     }
867
868
869     /**
870      * Start the Quartz Scheduler, respecting the "startupDelay" setting.
871      * @param scheduler the Scheduler to start
872      * @param startupDelay the number of seconds to wait before starting
873      * the Scheduler asynchronously
874      */

875     protected void startScheduler(final Scheduler scheduler, final int startupDelay) throws SchedulerException {
876         if (startupDelay <= 0) {
877             logger.info("Starting Quartz Scheduler now");
878             scheduler.start();
879         }
880         else {
881             if (logger.isInfoEnabled()) {
882                 logger.info("Will start Quartz Scheduler [" + scheduler.getSchedulerName() +
883                         "] in " + startupDelay + " seconds");
884             }
885             Thread JavaDoc schedulerThread = new Thread JavaDoc() {
886                 public void run() {
887                     try {
888                         Thread.sleep(startupDelay * 1000);
889                     }
890                     catch (InterruptedException JavaDoc ex) {
891                         // simply proceed
892
}
893                     if (logger.isInfoEnabled()) {
894                         logger.info("Starting Quartz Scheduler now, after delay of " + startupDelay + " seconds");
895                     }
896                     try {
897                         scheduler.start();
898                     }
899                     catch (SchedulerException ex) {
900                         throw new SchedulingException("Could not start Quartz Scheduler after delay", ex);
901                     }
902                 }
903             };
904             schedulerThread.setName("Quartz Scheduler [" + scheduler.getSchedulerName() + "]");
905             schedulerThread.start();
906         }
907     }
908
909
910     //---------------------------------------------------------------------
911
// Implementation of FactoryBean interface
912
//---------------------------------------------------------------------
913

914     public Object JavaDoc getObject() {
915         return this.scheduler;
916     }
917
918     public Class JavaDoc getObjectType() {
919         return (this.scheduler != null) ? this.scheduler.getClass() : Scheduler.class;
920     }
921
922     public boolean isSingleton() {
923         return true;
924     }
925
926
927     //---------------------------------------------------------------------
928
// Implementation of Lifecycle interface
929
//---------------------------------------------------------------------
930

931     public void start() throws SchedulingException {
932         if (this.scheduler != null) {
933             try {
934                 this.scheduler.start();
935             }
936             catch (SchedulerException ex) {
937                 throw new SchedulingException("Could not start Quartz Scheduler", ex);
938             }
939         }
940     }
941
942     public void stop() throws SchedulingException {
943         if (this.scheduler != null) {
944             try {
945                 this.scheduler.standby();
946             }
947             catch (SchedulerException ex) {
948                 throw new SchedulingException("Could not stop Quartz Scheduler", ex);
949             }
950         }
951     }
952
953     public boolean isRunning() throws SchedulingException {
954         if (this.scheduler != null) {
955             try {
956                 return !this.scheduler.isInStandbyMode();
957             }
958             catch (SchedulerException ex) {
959                 return false;
960             }
961         }
962         return false;
963     }
964
965
966     //---------------------------------------------------------------------
967
// Implementation of DisposableBean interface
968
//---------------------------------------------------------------------
969

970     /**
971      * Shut down the Quartz scheduler on bean factory shutdown,
972      * stopping all scheduled jobs.
973      */

974     public void destroy() throws SchedulerException {
975         logger.info("Shutting down Quartz Scheduler");
976         this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown);
977     }
978
979 }
980
Popular Tags