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

Save This Page
Home » Spring-Framework-090522 » org.springframework » scheduling » quartz » [javadoc | source]