0001: /*
0002: * Copyright 2002-2007 the original author or authors.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package org.springframework.scheduling.quartz;
0018:
0019: import java.io.IOException;
0020: import java.util.ArrayList;
0021: import java.util.Arrays;
0022: import java.util.Iterator;
0023: import java.util.LinkedList;
0024: import java.util.List;
0025: import java.util.Map;
0026: import java.util.Properties;
0027:
0028: import javax.sql.DataSource;
0029:
0030: import org.apache.commons.logging.Log;
0031: import org.apache.commons.logging.LogFactory;
0032: import org.quartz.Calendar;
0033: import org.quartz.JobDetail;
0034: import org.quartz.JobListener;
0035: import org.quartz.ObjectAlreadyExistsException;
0036: import org.quartz.Scheduler;
0037: import org.quartz.SchedulerException;
0038: import org.quartz.SchedulerFactory;
0039: import org.quartz.SchedulerListener;
0040: import org.quartz.Trigger;
0041: import org.quartz.TriggerListener;
0042: import org.quartz.impl.RemoteScheduler;
0043: import org.quartz.impl.StdSchedulerFactory;
0044: import org.quartz.simpl.SimpleThreadPool;
0045: import org.quartz.spi.JobFactory;
0046:
0047: import org.springframework.beans.BeanUtils;
0048: import org.springframework.beans.factory.DisposableBean;
0049: import org.springframework.beans.factory.FactoryBean;
0050: import org.springframework.beans.factory.InitializingBean;
0051: import org.springframework.context.ApplicationContext;
0052: import org.springframework.context.ApplicationContextAware;
0053: import org.springframework.context.Lifecycle;
0054: import org.springframework.core.io.Resource;
0055: import org.springframework.core.io.support.PropertiesLoaderUtils;
0056: import org.springframework.core.task.TaskExecutor;
0057: import org.springframework.scheduling.SchedulingException;
0058: import org.springframework.transaction.PlatformTransactionManager;
0059: import org.springframework.transaction.TransactionException;
0060: import org.springframework.transaction.TransactionStatus;
0061: import org.springframework.transaction.support.DefaultTransactionDefinition;
0062: import org.springframework.util.CollectionUtils;
0063:
0064: /**
0065: * FactoryBean that sets up a Quartz {@link org.quartz.Scheduler},
0066: * manages its lifecycle as part of the Spring application context,
0067: * and exposes the Scheduler reference for dependency injection.
0068: *
0069: * <p>Allows registration of JobDetails, Calendars and Triggers, automatically
0070: * starting the scheduler on initialization and shutting it down on destruction.
0071: * In scenarios that just require static registration of jobs at startup, there
0072: * is no need to access the Scheduler instance itself in application code.
0073: *
0074: * <p>For dynamic registration of jobs at runtime, use a bean reference to
0075: * this SchedulerFactoryBean to get direct access to the Quartz Scheduler
0076: * (<code>org.quartz.Scheduler</code>). This allows you to create new jobs
0077: * and triggers, and also to control and monitor the entire Scheduler.
0078: *
0079: * <p>Note that Quartz instantiates a new Job for each execution, in
0080: * contrast to Timer which uses a TimerTask instance that is shared
0081: * between repeated executions. Just JobDetail descriptors are shared.
0082: *
0083: * <p>When using persistent jobs, it is strongly recommended to perform all
0084: * operations on the Scheduler within Spring-managed (or plain JTA) transactions.
0085: * Else, database locking will not properly work and might even break.
0086: * (See {@link #setDataSource setDataSource} javadoc for details.)
0087: *
0088: * <p>The preferred way to achieve transactional execution is to demarcate
0089: * declarative transactions at the business facade level, which will
0090: * automatically apply to Scheduler operations performed within those scopes.
0091: * Alternatively, you may add transactional advice for the Scheduler itself.
0092: *
0093: * <p>This version of Spring's SchedulerFactoryBean requires Quartz 1.5 or higher.
0094: *
0095: * @author Juergen Hoeller
0096: * @since 18.02.2004
0097: * @see #setDataSource
0098: * @see org.quartz.Scheduler
0099: * @see org.quartz.SchedulerFactory
0100: * @see org.quartz.impl.StdSchedulerFactory
0101: * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean
0102: */
0103: public class SchedulerFactoryBean implements FactoryBean,
0104: ApplicationContextAware, InitializingBean, DisposableBean,
0105: Lifecycle {
0106:
0107: public static final String PROP_THREAD_COUNT = "org.quartz.threadPool.threadCount";
0108:
0109: public static final int DEFAULT_THREAD_COUNT = 10;
0110:
0111: private static final ThreadLocal configTimeTaskExecutorHolder = new ThreadLocal();
0112:
0113: private static final ThreadLocal configTimeDataSourceHolder = new ThreadLocal();
0114:
0115: private static final ThreadLocal configTimeNonTransactionalDataSourceHolder = new ThreadLocal();
0116:
0117: /**
0118: * Return the TaskExecutor for the currently configured Quartz Scheduler,
0119: * to be used by LocalTaskExecutorThreadPool.
0120: * <p>This instance will be set before initialization of the corresponding
0121: * Scheduler, and reset immediately afterwards. It is thus only available
0122: * during configuration.
0123: * @see #setDataSource
0124: * @see LocalDataSourceJobStore
0125: */
0126: public static TaskExecutor getConfigTimeTaskExecutor() {
0127: return (TaskExecutor) configTimeTaskExecutorHolder.get();
0128: }
0129:
0130: /**
0131: * Return the DataSource for the currently configured Quartz Scheduler,
0132: * to be used by LocalDataSourceJobStore.
0133: * <p>This instance will be set before initialization of the corresponding
0134: * Scheduler, and reset immediately afterwards. It is thus only available
0135: * during configuration.
0136: * @see #setDataSource
0137: * @see LocalDataSourceJobStore
0138: */
0139: public static DataSource getConfigTimeDataSource() {
0140: return (DataSource) configTimeDataSourceHolder.get();
0141: }
0142:
0143: /**
0144: * Return the non-transactional DataSource for the currently configured
0145: * Quartz Scheduler, to be used by LocalDataSourceJobStore.
0146: * <p>This instance will be set before initialization of the corresponding
0147: * Scheduler, and reset immediately afterwards. It is thus only available
0148: * during configuration.
0149: * @see #setNonTransactionalDataSource
0150: * @see LocalDataSourceJobStore
0151: */
0152: public static DataSource getConfigTimeNonTransactionalDataSource() {
0153: return (DataSource) configTimeNonTransactionalDataSourceHolder
0154: .get();
0155: }
0156:
0157: protected final Log logger = LogFactory.getLog(getClass());
0158:
0159: private Class schedulerFactoryClass = StdSchedulerFactory.class;
0160:
0161: private String schedulerName;
0162:
0163: private Resource configLocation;
0164:
0165: private Properties quartzProperties;
0166:
0167: private TaskExecutor taskExecutor;
0168:
0169: private DataSource dataSource;
0170:
0171: private DataSource nonTransactionalDataSource;
0172:
0173: private PlatformTransactionManager transactionManager;
0174:
0175: private Map schedulerContextMap;
0176:
0177: private ApplicationContext applicationContext;
0178:
0179: private String applicationContextSchedulerContextKey;
0180:
0181: private JobFactory jobFactory;
0182:
0183: private boolean jobFactorySet = false;
0184:
0185: private boolean overwriteExistingJobs = false;
0186:
0187: private String[] jobSchedulingDataLocations;
0188:
0189: private List jobDetails;
0190:
0191: private Map calendars;
0192:
0193: private List triggers;
0194:
0195: private SchedulerListener[] schedulerListeners;
0196:
0197: private JobListener[] globalJobListeners;
0198:
0199: private JobListener[] jobListeners;
0200:
0201: private TriggerListener[] globalTriggerListeners;
0202:
0203: private TriggerListener[] triggerListeners;
0204:
0205: private boolean autoStartup = true;
0206:
0207: private int startupDelay = 0;
0208:
0209: private boolean waitForJobsToCompleteOnShutdown = false;
0210:
0211: private Scheduler scheduler;
0212:
0213: /**
0214: * Set the Quartz SchedulerFactory implementation to use.
0215: * <p>Default is StdSchedulerFactory, reading in the standard
0216: * quartz.properties from quartz.jar. To use custom Quartz
0217: * properties, specify "configLocation" or "quartzProperties".
0218: * @see org.quartz.impl.StdSchedulerFactory
0219: * @see #setConfigLocation
0220: * @see #setQuartzProperties
0221: */
0222: public void setSchedulerFactoryClass(Class schedulerFactoryClass) {
0223: if (schedulerFactoryClass == null
0224: || !SchedulerFactory.class
0225: .isAssignableFrom(schedulerFactoryClass)) {
0226: throw new IllegalArgumentException(
0227: "schedulerFactoryClass must implement [org.quartz.SchedulerFactory]");
0228: }
0229: this .schedulerFactoryClass = schedulerFactoryClass;
0230: }
0231:
0232: /**
0233: * Set the name of the Scheduler to fetch from the SchedulerFactory.
0234: * If not specified, the default Scheduler will be used.
0235: * @see org.quartz.SchedulerFactory#getScheduler(String)
0236: * @see org.quartz.SchedulerFactory#getScheduler
0237: */
0238: public void setSchedulerName(String schedulerName) {
0239: this .schedulerName = schedulerName;
0240: }
0241:
0242: /**
0243: * Set the location of the Quartz properties config file, for example
0244: * as classpath resource "classpath:quartz.properties".
0245: * <p>Note: Can be omitted when all necessary properties are specified
0246: * locally via this bean, or when relying on Quartz' default configuration.
0247: * @see #setQuartzProperties
0248: */
0249: public void setConfigLocation(Resource configLocation) {
0250: this .configLocation = configLocation;
0251: }
0252:
0253: /**
0254: * Set Quartz properties, like "org.quartz.threadPool.class".
0255: * <p>Can be used to override values in a Quartz properties config file,
0256: * or to specify all necessary properties locally.
0257: * @see #setConfigLocation
0258: */
0259: public void setQuartzProperties(Properties quartzProperties) {
0260: this .quartzProperties = quartzProperties;
0261: }
0262:
0263: /**
0264: * Set the Spring TaskExecutor to use as Quartz backend.
0265: * Exposed as thread pool through the Quartz SPI.
0266: * <p>Can be used to assign a JDK 1.5 ThreadPoolExecutor or a CommonJ
0267: * WorkManager as Quartz backend, to avoid Quartz's manual thread creation.
0268: * <p>By default, a Quartz SimpleThreadPool will be used, configured through
0269: * the corresponding Quartz properties.
0270: * @see #setQuartzProperties
0271: * @see LocalTaskExecutorThreadPool
0272: * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
0273: * @see org.springframework.scheduling.commonj.WorkManagerTaskExecutor
0274: */
0275: public void setTaskExecutor(TaskExecutor taskExecutor) {
0276: this .taskExecutor = taskExecutor;
0277: }
0278:
0279: /**
0280: * Set the default DataSource to be used by the Scheduler. If set,
0281: * this will override corresponding settings in Quartz properties.
0282: * <p>Note: If this is set, the Quartz settings should not define
0283: * a job store "dataSource" to avoid meaningless double configuration.
0284: * <p>A Spring-specific subclass of Quartz' JobStoreCMT will be used.
0285: * It is therefore strongly recommended to perform all operations on
0286: * the Scheduler within Spring-managed (or plain JTA) transactions.
0287: * Else, database locking will not properly work and might even break
0288: * (e.g. if trying to obtain a lock on Oracle without a transaction).
0289: * <p>Supports both transactional and non-transactional DataSource access.
0290: * With a non-XA DataSource and local Spring transactions, a single DataSource
0291: * argument is sufficient. In case of an XA DataSource and global JTA transactions,
0292: * SchedulerFactoryBean's "nonTransactionalDataSource" property should be set,
0293: * passing in a non-XA DataSource that will not participate in global transactions.
0294: * @see #setNonTransactionalDataSource
0295: * @see #setQuartzProperties
0296: * @see #setTransactionManager
0297: * @see LocalDataSourceJobStore
0298: */
0299: public void setDataSource(DataSource dataSource) {
0300: this .dataSource = dataSource;
0301: }
0302:
0303: /**
0304: * Set the DataSource to be used by the Scheduler <i>for non-transactional access</i>.
0305: * <p>This is only necessary if the default DataSource is an XA DataSource that will
0306: * always participate in transactions: A non-XA version of that DataSource should
0307: * be specified as "nonTransactionalDataSource" in such a scenario.
0308: * <p>This is not relevant with a local DataSource instance and Spring transactions.
0309: * Specifying a single default DataSource as "dataSource" is sufficient there.
0310: * @see #setDataSource
0311: * @see LocalDataSourceJobStore
0312: */
0313: public void setNonTransactionalDataSource(
0314: DataSource nonTransactionalDataSource) {
0315: this .nonTransactionalDataSource = nonTransactionalDataSource;
0316: }
0317:
0318: /**
0319: * Set the transaction manager to be used for registering jobs and triggers
0320: * that are defined by this SchedulerFactoryBean. Default is none; setting
0321: * this only makes sense when specifying a DataSource for the Scheduler.
0322: * @see #setDataSource
0323: */
0324: public void setTransactionManager(
0325: PlatformTransactionManager transactionManager) {
0326: this .transactionManager = transactionManager;
0327: }
0328:
0329: /**
0330: * Register objects in the Scheduler context via a given Map.
0331: * These objects will be available to any Job that runs in this Scheduler.
0332: * <p>Note: When using persistent Jobs whose JobDetail will be kept in the
0333: * database, do not put Spring-managed beans or an ApplicationContext
0334: * reference into the JobDataMap but rather into the SchedulerContext.
0335: * @param schedulerContextAsMap Map with String keys and any objects as
0336: * values (for example Spring-managed beans)
0337: * @see JobDetailBean#setJobDataAsMap
0338: */
0339: public void setSchedulerContextAsMap(Map schedulerContextAsMap) {
0340: this .schedulerContextMap = schedulerContextAsMap;
0341: }
0342:
0343: public void setApplicationContext(
0344: ApplicationContext applicationContext) {
0345: this .applicationContext = applicationContext;
0346: }
0347:
0348: /**
0349: * Set the key of an ApplicationContext reference to expose in the
0350: * SchedulerContext, for example "applicationContext". Default is none.
0351: * Only applicable when running in a Spring ApplicationContext.
0352: * <p>Note: When using persistent Jobs whose JobDetail will be kept in the
0353: * database, do not put an ApplicationContext reference into the JobDataMap
0354: * but rather into the SchedulerContext.
0355: * <p>In case of a QuartzJobBean, the reference will be applied to the Job
0356: * instance as bean property. An "applicationContext" attribute will
0357: * correspond to a "setApplicationContext" method in that scenario.
0358: * <p>Note that BeanFactory callback interfaces like ApplicationContextAware
0359: * are not automatically applied to Quartz Job instances, because Quartz
0360: * itself is reponsible for the lifecycle of its Jobs.
0361: * @see JobDetailBean#setApplicationContextJobDataKey
0362: * @see org.springframework.context.ApplicationContext
0363: */
0364: public void setApplicationContextSchedulerContextKey(
0365: String applicationContextSchedulerContextKey) {
0366: this .applicationContextSchedulerContextKey = applicationContextSchedulerContextKey;
0367: }
0368:
0369: /**
0370: * Set the Quartz JobFactory to use for this Scheduler.
0371: * <p>Default is Spring's {@link AdaptableJobFactory}, which supports
0372: * {@link java.lang.Runnable} objects as well as standard Quartz
0373: * {@link org.quartz.Job} instances. Note that this default only applies
0374: * to a <i>local</i> Scheduler, not to a RemoteScheduler (where setting
0375: * a custom JobFactory is not supported by Quartz).
0376: * <p>Specify an instance of Spring's {@link SpringBeanJobFactory} here
0377: * (typically as an inner bean definition) to automatically populate a job's
0378: * bean properties from the specified job data map and scheduler context.
0379: * @see AdaptableJobFactory
0380: * @see SpringBeanJobFactory
0381: */
0382: public void setJobFactory(JobFactory jobFactory) {
0383: this .jobFactory = jobFactory;
0384: this .jobFactorySet = true;
0385: }
0386:
0387: /**
0388: * Set whether any jobs defined on this SchedulerFactoryBean should overwrite
0389: * existing job definitions. Default is "false", to not overwrite already
0390: * registered jobs that have been read in from a persistent job store.
0391: */
0392: public void setOverwriteExistingJobs(boolean overwriteExistingJobs) {
0393: this .overwriteExistingJobs = overwriteExistingJobs;
0394: }
0395:
0396: /**
0397: * Set the location of a Quartz job definition XML file that follows the
0398: * "job_scheduling_data_1_0" DTD. Can be specified to automatically
0399: * register jobs that are defined in such a file, possibly in addition
0400: * to jobs defined directly on this SchedulerFactoryBean.
0401: * @see ResourceJobSchedulingDataProcessor
0402: * @see org.quartz.xml.JobSchedulingDataProcessor
0403: */
0404: public void setJobSchedulingDataLocation(
0405: String jobSchedulingDataLocation) {
0406: this .jobSchedulingDataLocations = new String[] { jobSchedulingDataLocation };
0407: }
0408:
0409: /**
0410: * Set the locations of Quartz job definition XML files that follow the
0411: * "job_scheduling_data_1_0" DTD. Can be specified to automatically
0412: * register jobs that are defined in such files, possibly in addition
0413: * to jobs defined directly on this SchedulerFactoryBean.
0414: * @see ResourceJobSchedulingDataProcessor
0415: * @see org.quartz.xml.JobSchedulingDataProcessor
0416: */
0417: public void setJobSchedulingDataLocations(
0418: String[] jobSchedulingDataLocations) {
0419: this .jobSchedulingDataLocations = jobSchedulingDataLocations;
0420: }
0421:
0422: /**
0423: * Register a list of JobDetail objects with the Scheduler that
0424: * this FactoryBean creates, to be referenced by Triggers.
0425: * <p>This is not necessary when a Trigger determines the JobDetail
0426: * itself: In this case, the JobDetail will be implicitly registered
0427: * in combination with the Trigger.
0428: * @see #setTriggers
0429: * @see org.quartz.JobDetail
0430: * @see JobDetailBean
0431: * @see JobDetailAwareTrigger
0432: * @see org.quartz.Trigger#setJobName
0433: */
0434: public void setJobDetails(JobDetail[] jobDetails) {
0435: // Use modifiable ArrayList here, to allow for further adding of
0436: // JobDetail objects during autodetection of JobDetailAwareTriggers.
0437: this .jobDetails = new ArrayList(Arrays.asList(jobDetails));
0438: }
0439:
0440: /**
0441: * Register a list of Quartz Calendar objects with the Scheduler
0442: * that this FactoryBean creates, to be referenced by Triggers.
0443: * @param calendars Map with calendar names as keys as Calendar
0444: * objects as values
0445: * @see org.quartz.Calendar
0446: * @see org.quartz.Trigger#setCalendarName
0447: */
0448: public void setCalendars(Map calendars) {
0449: this .calendars = calendars;
0450: }
0451:
0452: /**
0453: * Register a list of Trigger objects with the Scheduler that
0454: * this FactoryBean creates.
0455: * <p>If the Trigger determines the corresponding JobDetail itself,
0456: * the job will be automatically registered with the Scheduler.
0457: * Else, the respective JobDetail needs to be registered via the
0458: * "jobDetails" property of this FactoryBean.
0459: * @see #setJobDetails
0460: * @see org.quartz.JobDetail
0461: * @see JobDetailAwareTrigger
0462: * @see CronTriggerBean
0463: * @see SimpleTriggerBean
0464: */
0465: public void setTriggers(Trigger[] triggers) {
0466: this .triggers = Arrays.asList(triggers);
0467: }
0468:
0469: /**
0470: * Specify Quartz SchedulerListeners to be registered with the Scheduler.
0471: */
0472: public void setSchedulerListeners(
0473: SchedulerListener[] schedulerListeners) {
0474: this .schedulerListeners = schedulerListeners;
0475: }
0476:
0477: /**
0478: * Specify global Quartz JobListeners to be registered with the Scheduler.
0479: * Such JobListeners will apply to all Jobs in the Scheduler.
0480: */
0481: public void setGlobalJobListeners(JobListener[] globalJobListeners) {
0482: this .globalJobListeners = globalJobListeners;
0483: }
0484:
0485: /**
0486: * Specify named Quartz JobListeners to be registered with the Scheduler.
0487: * Such JobListeners will only apply to Jobs that explicitly activate
0488: * them via their name.
0489: * @see org.quartz.JobListener#getName
0490: * @see org.quartz.JobDetail#addJobListener
0491: * @see JobDetailBean#setJobListenerNames
0492: */
0493: public void setJobListeners(JobListener[] jobListeners) {
0494: this .jobListeners = jobListeners;
0495: }
0496:
0497: /**
0498: * Specify global Quartz TriggerListeners to be registered with the Scheduler.
0499: * Such TriggerListeners will apply to all Triggers in the Scheduler.
0500: */
0501: public void setGlobalTriggerListeners(
0502: TriggerListener[] globalTriggerListeners) {
0503: this .globalTriggerListeners = globalTriggerListeners;
0504: }
0505:
0506: /**
0507: * Specify named Quartz TriggerListeners to be registered with the Scheduler.
0508: * Such TriggerListeners will only apply to Triggers that explicitly activate
0509: * them via their name.
0510: * @see org.quartz.TriggerListener#getName
0511: * @see org.quartz.Trigger#addTriggerListener
0512: * @see CronTriggerBean#setTriggerListenerNames
0513: * @see SimpleTriggerBean#setTriggerListenerNames
0514: */
0515: public void setTriggerListeners(TriggerListener[] triggerListeners) {
0516: this .triggerListeners = triggerListeners;
0517: }
0518:
0519: /**
0520: * Set whether to automatically start the scheduler after initialization.
0521: * Default is "true"; set this to "false" to allow for manual startup.
0522: */
0523: public void setAutoStartup(boolean autoStartup) {
0524: this .autoStartup = autoStartup;
0525: }
0526:
0527: /**
0528: * Set the number of seconds to wait after initialization before
0529: * starting the scheduler asynchronously. Default is 0, meaning
0530: * immediate synchronous startup on initialization of this bean.
0531: * <p>Setting this to 10 or 20 seconds makes sense if no jobs
0532: * should be run before the entire application has started up.
0533: */
0534: public void setStartupDelay(int startupDelay) {
0535: this .startupDelay = startupDelay;
0536: }
0537:
0538: /**
0539: * Set whether to wait for running jobs to complete on shutdown.
0540: * <p>Default is "false". Switch this to "true" if you prefer
0541: * fully completed jobs at the expense of a longer shutdown phase.
0542: * @see org.quartz.Scheduler#shutdown(boolean)
0543: */
0544: public void setWaitForJobsToCompleteOnShutdown(
0545: boolean waitForJobsToCompleteOnShutdown) {
0546: this .waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
0547: }
0548:
0549: //---------------------------------------------------------------------
0550: // Implementation of InitializingBean interface
0551: //---------------------------------------------------------------------
0552:
0553: public void afterPropertiesSet() throws Exception {
0554: if (this .dataSource == null
0555: && this .nonTransactionalDataSource != null) {
0556: this .dataSource = this .nonTransactionalDataSource;
0557: }
0558:
0559: // Create SchedulerFactory instance.
0560: SchedulerFactory schedulerFactory = (SchedulerFactory) BeanUtils
0561: .instantiateClass(this .schedulerFactoryClass);
0562:
0563: initSchedulerFactory(schedulerFactory);
0564:
0565: if (this .taskExecutor != null) {
0566: // Make given TaskExecutor available for SchedulerFactory configuration.
0567: configTimeTaskExecutorHolder.set(this .taskExecutor);
0568: }
0569: if (this .dataSource != null) {
0570: // Make given DataSource available for SchedulerFactory configuration.
0571: configTimeDataSourceHolder.set(this .dataSource);
0572: }
0573: if (this .nonTransactionalDataSource != null) {
0574: // Make given non-transactional DataSource available for SchedulerFactory configuration.
0575: configTimeNonTransactionalDataSourceHolder
0576: .set(this .nonTransactionalDataSource);
0577: }
0578:
0579: // Get Scheduler instance from SchedulerFactory.
0580: try {
0581: this .scheduler = createScheduler(schedulerFactory,
0582: this .schedulerName);
0583: if (!this .jobFactorySet
0584: && !(this .scheduler instanceof RemoteScheduler)) {
0585: // Use AdaptableJobFactory as default for a local Scheduler, unless when
0586: // explicitly given a null value through the "jobFactory" bean property.
0587: this .jobFactory = new AdaptableJobFactory();
0588: }
0589: if (this .jobFactory != null) {
0590: if (this .jobFactory instanceof SchedulerContextAware) {
0591: ((SchedulerContextAware) this .jobFactory)
0592: .setSchedulerContext(this .scheduler
0593: .getContext());
0594: }
0595: this .scheduler.setJobFactory(this .jobFactory);
0596: }
0597: }
0598:
0599: finally {
0600: if (this .taskExecutor != null) {
0601: configTimeTaskExecutorHolder.set(null);
0602: }
0603: if (this .dataSource != null) {
0604: configTimeDataSourceHolder.set(null);
0605: }
0606: if (this .nonTransactionalDataSource != null) {
0607: configTimeNonTransactionalDataSourceHolder.set(null);
0608: }
0609: }
0610:
0611: populateSchedulerContext();
0612:
0613: registerListeners();
0614:
0615: registerJobsAndTriggers();
0616:
0617: // Start Scheduler immediately, if demanded.
0618: if (this .autoStartup) {
0619: startScheduler(this .scheduler, this .startupDelay);
0620: }
0621: }
0622:
0623: /**
0624: * Load and/or apply Quartz properties to the given SchedulerFactory.
0625: * @param schedulerFactory the SchedulerFactory to initialize
0626: */
0627: private void initSchedulerFactory(SchedulerFactory schedulerFactory)
0628: throws SchedulerException, IOException {
0629:
0630: if (this .configLocation != null
0631: || this .quartzProperties != null
0632: || this .dataSource != null
0633: || this .schedulerName != null
0634: || this .taskExecutor != null) {
0635:
0636: if (!(schedulerFactory instanceof StdSchedulerFactory)) {
0637: throw new IllegalArgumentException(
0638: "StdSchedulerFactory required for applying Quartz properties");
0639: }
0640:
0641: Properties mergedProps = new Properties();
0642:
0643: // Set necessary default properties here, as Quartz will not apply
0644: // its default configuration when explicitly given properties.
0645: if (this .taskExecutor != null) {
0646: mergedProps.setProperty(
0647: StdSchedulerFactory.PROP_THREAD_POOL_CLASS,
0648: LocalTaskExecutorThreadPool.class.getName());
0649: } else {
0650: mergedProps.setProperty(
0651: StdSchedulerFactory.PROP_THREAD_POOL_CLASS,
0652: SimpleThreadPool.class.getName());
0653: mergedProps.setProperty(PROP_THREAD_COUNT, Integer
0654: .toString(DEFAULT_THREAD_COUNT));
0655: }
0656:
0657: if (this .configLocation != null) {
0658: if (logger.isInfoEnabled()) {
0659: logger.info("Loading Quartz config from ["
0660: + this .configLocation + "]");
0661: }
0662: PropertiesLoaderUtils.fillProperties(mergedProps,
0663: this .configLocation);
0664: }
0665:
0666: CollectionUtils.mergePropertiesIntoMap(
0667: this .quartzProperties, mergedProps);
0668:
0669: if (this .dataSource != null) {
0670: mergedProps.put(
0671: StdSchedulerFactory.PROP_JOB_STORE_CLASS,
0672: LocalDataSourceJobStore.class.getName());
0673: }
0674:
0675: // Make sure to set the scheduler name as configured in the Spring configuration.
0676: if (this .schedulerName != null) {
0677: mergedProps.put(
0678: StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME,
0679: this .schedulerName);
0680: }
0681:
0682: ((StdSchedulerFactory) schedulerFactory)
0683: .initialize(mergedProps);
0684: }
0685: }
0686:
0687: /**
0688: * Create the Scheduler instance for the given factory and scheduler name.
0689: * Called by afterPropertiesSet.
0690: * <p>Default implementation invokes SchedulerFactory's <code>getScheduler</code>
0691: * method. Can be overridden for custom Scheduler creation.
0692: * @param schedulerFactory the factory to create the Scheduler with
0693: * @param schedulerName the name of the scheduler to create
0694: * @return the Scheduler instance
0695: * @throws SchedulerException if thrown by Quartz methods
0696: * @see #afterPropertiesSet
0697: * @see org.quartz.SchedulerFactory#getScheduler
0698: */
0699: protected Scheduler createScheduler(
0700: SchedulerFactory schedulerFactory, String schedulerName)
0701: throws SchedulerException {
0702:
0703: // StdSchedulerFactory's default "getScheduler" implementation
0704: // uses the scheduler name specified in the Quartz properties,
0705: // which we have set before (in "initSchedulerFactory").
0706: return schedulerFactory.getScheduler();
0707: }
0708:
0709: /**
0710: * Expose the specified context attributes and/or the current
0711: * ApplicationContext in the Quartz SchedulerContext.
0712: */
0713: private void populateSchedulerContext() throws SchedulerException {
0714: // Put specified objects into Scheduler context.
0715: if (this .schedulerContextMap != null) {
0716: this .scheduler.getContext()
0717: .putAll(this .schedulerContextMap);
0718: }
0719:
0720: // Register ApplicationContext in Scheduler context.
0721: if (this .applicationContextSchedulerContextKey != null) {
0722: if (this .applicationContext == null) {
0723: throw new IllegalStateException(
0724: "SchedulerFactoryBean needs to be set up in an ApplicationContext "
0725: + "to be able to handle an 'applicationContextSchedulerContextKey'");
0726: }
0727: this .scheduler.getContext().put(
0728: this .applicationContextSchedulerContextKey,
0729: this .applicationContext);
0730: }
0731: }
0732:
0733: /**
0734: * Register all specified listeners with the Scheduler.
0735: */
0736: private void registerListeners() throws SchedulerException {
0737: if (this .schedulerListeners != null) {
0738: for (int i = 0; i < this .schedulerListeners.length; i++) {
0739: this .scheduler
0740: .addSchedulerListener(this .schedulerListeners[i]);
0741: }
0742: }
0743: if (this .globalJobListeners != null) {
0744: for (int i = 0; i < this .globalJobListeners.length; i++) {
0745: this .scheduler
0746: .addGlobalJobListener(this .globalJobListeners[i]);
0747: }
0748: }
0749: if (this .jobListeners != null) {
0750: for (int i = 0; i < this .jobListeners.length; i++) {
0751: this .scheduler.addJobListener(this .jobListeners[i]);
0752: }
0753: }
0754: if (this .globalTriggerListeners != null) {
0755: for (int i = 0; i < this .globalTriggerListeners.length; i++) {
0756: this .scheduler
0757: .addGlobalTriggerListener(this .globalTriggerListeners[i]);
0758: }
0759: }
0760: if (this .triggerListeners != null) {
0761: for (int i = 0; i < this .triggerListeners.length; i++) {
0762: this .scheduler
0763: .addTriggerListener(this .triggerListeners[i]);
0764: }
0765: }
0766: }
0767:
0768: /**
0769: * Register jobs and triggers (within a transaction, if possible).
0770: */
0771: private void registerJobsAndTriggers() throws SchedulerException {
0772: TransactionStatus transactionStatus = null;
0773: if (this .transactionManager != null) {
0774: transactionStatus = this .transactionManager
0775: .getTransaction(new DefaultTransactionDefinition());
0776: }
0777: try {
0778:
0779: if (this .jobSchedulingDataLocations != null) {
0780: ResourceJobSchedulingDataProcessor dataProcessor = new ResourceJobSchedulingDataProcessor();
0781: if (this .applicationContext != null) {
0782: dataProcessor
0783: .setResourceLoader(this .applicationContext);
0784: }
0785: for (int i = 0; i < this .jobSchedulingDataLocations.length; i++) {
0786: dataProcessor.processFileAndScheduleJobs(
0787: this .jobSchedulingDataLocations[i],
0788: this .scheduler, this .overwriteExistingJobs);
0789: }
0790: }
0791:
0792: // Register JobDetails.
0793: if (this .jobDetails != null) {
0794: for (Iterator it = this .jobDetails.iterator(); it
0795: .hasNext();) {
0796: JobDetail jobDetail = (JobDetail) it.next();
0797: addJobToScheduler(jobDetail);
0798: }
0799: } else {
0800: // Create empty list for easier checks when registering triggers.
0801: this .jobDetails = new LinkedList();
0802: }
0803:
0804: // Register Calendars.
0805: if (this .calendars != null) {
0806: for (Iterator it = this .calendars.keySet().iterator(); it
0807: .hasNext();) {
0808: String calendarName = (String) it.next();
0809: Calendar calendar = (Calendar) this .calendars
0810: .get(calendarName);
0811: this .scheduler.addCalendar(calendarName, calendar,
0812: true, true);
0813: }
0814: }
0815:
0816: // Register Triggers.
0817: if (this .triggers != null) {
0818: for (Iterator it = this .triggers.iterator(); it
0819: .hasNext();) {
0820: Trigger trigger = (Trigger) it.next();
0821: addTriggerToScheduler(trigger);
0822: }
0823: }
0824: }
0825:
0826: catch (Throwable ex) {
0827: if (transactionStatus != null) {
0828: try {
0829: this .transactionManager.rollback(transactionStatus);
0830: } catch (TransactionException tex) {
0831: logger
0832: .error(
0833: "Job registration exception overridden by rollback exception",
0834: ex);
0835: throw tex;
0836: }
0837: }
0838: if (ex instanceof SchedulerException) {
0839: throw (SchedulerException) ex;
0840: }
0841: if (ex instanceof Exception) {
0842: throw new SchedulerException(
0843: "Registration of jobs and triggers failed: "
0844: + ex.getMessage(), (Exception) ex);
0845: }
0846: throw new SchedulerException(
0847: "Registration of jobs and triggers failed: "
0848: + ex.getMessage());
0849: }
0850:
0851: if (transactionStatus != null) {
0852: this .transactionManager.commit(transactionStatus);
0853: }
0854: }
0855:
0856: /**
0857: * Add the given job to the Scheduler, if it doesn't already exist.
0858: * Overwrites the job in any case if "overwriteExistingJobs" is set.
0859: * @param jobDetail the job to add
0860: * @return <code>true</code> if the job was actually added,
0861: * <code>false</code> if it already existed before
0862: * @see #setOverwriteExistingJobs
0863: */
0864: private boolean addJobToScheduler(JobDetail jobDetail)
0865: throws SchedulerException {
0866: if (this .overwriteExistingJobs
0867: || this .scheduler.getJobDetail(jobDetail.getName(),
0868: jobDetail.getGroup()) == null) {
0869: this .scheduler.addJob(jobDetail, true);
0870: return true;
0871: } else {
0872: return false;
0873: }
0874: }
0875:
0876: /**
0877: * Add the given trigger to the Scheduler, if it doesn't already exist.
0878: * Overwrites the trigger in any case if "overwriteExistingJobs" is set.
0879: * @param trigger the trigger to add
0880: * @return <code>true</code> if the trigger was actually added,
0881: * <code>false</code> if it already existed before
0882: * @see #setOverwriteExistingJobs
0883: */
0884: private boolean addTriggerToScheduler(Trigger trigger)
0885: throws SchedulerException {
0886: boolean triggerExists = (this .scheduler.getTrigger(trigger
0887: .getName(), trigger.getGroup()) != null);
0888: if (!triggerExists || this .overwriteExistingJobs) {
0889: // Check if the Trigger is aware of an associated JobDetail.
0890: if (trigger instanceof JobDetailAwareTrigger) {
0891: JobDetail jobDetail = ((JobDetailAwareTrigger) trigger)
0892: .getJobDetail();
0893: // Automatically register the JobDetail too.
0894: if (!this .jobDetails.contains(jobDetail)
0895: && addJobToScheduler(jobDetail)) {
0896: this .jobDetails.add(jobDetail);
0897: }
0898: }
0899: if (!triggerExists) {
0900: try {
0901: this .scheduler.scheduleJob(trigger);
0902: } catch (ObjectAlreadyExistsException ex) {
0903: if (logger.isDebugEnabled()) {
0904: logger
0905: .debug("Unexpectedly found existing trigger, assumably due to cluster race condition: "
0906: + ex.getMessage()
0907: + " - can safely be ignored");
0908: }
0909: if (this .overwriteExistingJobs) {
0910: this .scheduler.rescheduleJob(trigger.getName(),
0911: trigger.getGroup(), trigger);
0912: }
0913: }
0914: } else {
0915: this .scheduler.rescheduleJob(trigger.getName(), trigger
0916: .getGroup(), trigger);
0917: }
0918: return true;
0919: } else {
0920: return false;
0921: }
0922: }
0923:
0924: /**
0925: * Start the Quartz Scheduler, respecting the "startupDelay" setting.
0926: * @param scheduler the Scheduler to start
0927: * @param startupDelay the number of seconds to wait before starting
0928: * the Scheduler asynchronously
0929: */
0930: protected void startScheduler(final Scheduler scheduler,
0931: final int startupDelay) throws SchedulerException {
0932: if (startupDelay <= 0) {
0933: logger.info("Starting Quartz Scheduler now");
0934: scheduler.start();
0935: } else {
0936: if (logger.isInfoEnabled()) {
0937: logger.info("Will start Quartz Scheduler ["
0938: + scheduler.getSchedulerName() + "] in "
0939: + startupDelay + " seconds");
0940: }
0941: Thread schedulerThread = new Thread() {
0942: public void run() {
0943: try {
0944: Thread.sleep(startupDelay * 1000);
0945: } catch (InterruptedException ex) {
0946: // simply proceed
0947: }
0948: if (logger.isInfoEnabled()) {
0949: logger
0950: .info("Starting Quartz Scheduler now, after delay of "
0951: + startupDelay + " seconds");
0952: }
0953: try {
0954: scheduler.start();
0955: } catch (SchedulerException ex) {
0956: throw new SchedulingException(
0957: "Could not start Quartz Scheduler after delay",
0958: ex);
0959: }
0960: }
0961: };
0962: schedulerThread.setName("Quartz Scheduler ["
0963: + scheduler.getSchedulerName() + "]");
0964: schedulerThread.start();
0965: }
0966: }
0967:
0968: //---------------------------------------------------------------------
0969: // Implementation of FactoryBean interface
0970: //---------------------------------------------------------------------
0971:
0972: public Object getObject() {
0973: return this .scheduler;
0974: }
0975:
0976: public Class getObjectType() {
0977: return (this .scheduler != null) ? this .scheduler.getClass()
0978: : Scheduler.class;
0979: }
0980:
0981: public boolean isSingleton() {
0982: return true;
0983: }
0984:
0985: //---------------------------------------------------------------------
0986: // Implementation of Lifecycle interface
0987: //---------------------------------------------------------------------
0988:
0989: public void start() throws SchedulingException {
0990: if (this .scheduler != null) {
0991: try {
0992: this .scheduler.start();
0993: } catch (SchedulerException ex) {
0994: throw new SchedulingException(
0995: "Could not start Quartz Scheduler", ex);
0996: }
0997: }
0998: }
0999:
1000: public void stop() throws SchedulingException {
1001: if (this .scheduler != null) {
1002: try {
1003: this .scheduler.standby();
1004: } catch (SchedulerException ex) {
1005: throw new SchedulingException(
1006: "Could not stop Quartz Scheduler", ex);
1007: }
1008: }
1009: }
1010:
1011: public boolean isRunning() throws SchedulingException {
1012: if (this .scheduler != null) {
1013: try {
1014: return !this .scheduler.isInStandbyMode();
1015: } catch (SchedulerException ex) {
1016: return false;
1017: }
1018: }
1019: return false;
1020: }
1021:
1022: //---------------------------------------------------------------------
1023: // Implementation of DisposableBean interface
1024: //---------------------------------------------------------------------
1025:
1026: /**
1027: * Shut down the Quartz scheduler on bean factory shutdown,
1028: * stopping all scheduled jobs.
1029: */
1030: public void destroy() throws SchedulerException {
1031: logger.info("Shutting down Quartz Scheduler");
1032: this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown);
1033: }
1034:
1035: }
|