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