Skip to content

Commit 33d655a

Browse files
committed
Consistent handling of InterruptedException (plus setSchedulerFactory)
Issue: SPR-16479 Issue: SPR-16439 (cherry picked from commit 39201ad)
1 parent 2654dbf commit 33d655a

File tree

6 files changed

+109
-76
lines changed

6 files changed

+109
-76
lines changed

spring-context-support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java

Lines changed: 94 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -105,11 +105,10 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
105105
new ThreadLocal<DataSource>();
106106

107107
/**
108-
* Return the ResourceLoader for the currently configured Quartz Scheduler,
109-
* to be used by ResourceLoaderClassLoadHelper.
110-
* <p>This instance will be set before initialization of the corresponding
111-
* Scheduler, and reset immediately afterwards. It is thus only available
112-
* during configuration.
108+
* Return the {@link ResourceLoader} for the currently configured Quartz Scheduler,
109+
* to be used by {@link ResourceLoaderClassLoadHelper}.
110+
* <p>This instance will be set before initialization of the corresponding Scheduler,
111+
* and reset immediately afterwards. It is thus only available during configuration.
113112
* @see #setApplicationContext
114113
* @see ResourceLoaderClassLoadHelper
115114
*/
@@ -118,11 +117,11 @@ public static ResourceLoader getConfigTimeResourceLoader() {
118117
}
119118

120119
/**
121-
* Return the TaskExecutor for the currently configured Quartz Scheduler,
122-
* to be used by LocalTaskExecutorThreadPool.
123-
* <p>This instance will be set before initialization of the corresponding
124-
* Scheduler, and reset immediately afterwards. It is thus only available
125-
* during configuration.
120+
* Return the {@link Executor} for the currently configured Quartz Scheduler,
121+
* to be used by {@link LocalTaskExecutorThreadPool}.
122+
* <p>This instance will be set before initialization of the corresponding Scheduler,
123+
* and reset immediately afterwards. It is thus only available during configuration.
124+
* @since 2.0
126125
* @see #setTaskExecutor
127126
* @see LocalTaskExecutorThreadPool
128127
*/
@@ -131,11 +130,11 @@ public static Executor getConfigTimeTaskExecutor() {
131130
}
132131

133132
/**
134-
* Return the DataSource for the currently configured Quartz Scheduler,
135-
* to be used by LocalDataSourceJobStore.
136-
* <p>This instance will be set before initialization of the corresponding
137-
* Scheduler, and reset immediately afterwards. It is thus only available
138-
* during configuration.
133+
* Return the {@link DataSource} for the currently configured Quartz Scheduler,
134+
* to be used by {@link LocalDataSourceJobStore}.
135+
* <p>This instance will be set before initialization of the corresponding Scheduler,
136+
* and reset immediately afterwards. It is thus only available during configuration.
137+
* @since 1.1
139138
* @see #setDataSource
140139
* @see LocalDataSourceJobStore
141140
*/
@@ -144,11 +143,11 @@ public static DataSource getConfigTimeDataSource() {
144143
}
145144

146145
/**
147-
* Return the non-transactional DataSource for the currently configured
148-
* Quartz Scheduler, to be used by LocalDataSourceJobStore.
149-
* <p>This instance will be set before initialization of the corresponding
150-
* Scheduler, and reset immediately afterwards. It is thus only available
151-
* during configuration.
146+
* Return the non-transactional {@link DataSource} for the currently configured
147+
* Quartz Scheduler, to be used by {@link LocalDataSourceJobStore}.
148+
* <p>This instance will be set before initialization of the corresponding Scheduler,
149+
* and reset immediately afterwards. It is thus only available during configuration.
150+
* @since 1.1
152151
* @see #setNonTransactionalDataSource
153152
* @see LocalDataSourceJobStore
154153
*/
@@ -157,6 +156,8 @@ public static DataSource getConfigTimeNonTransactionalDataSource() {
157156
}
158157

159158

159+
private SchedulerFactory schedulerFactory;
160+
160161
private Class<? extends SchedulerFactory> schedulerFactoryClass = StdSchedulerFactory.class;
161162

162163
private String schedulerName;
@@ -165,14 +166,12 @@ public static DataSource getConfigTimeNonTransactionalDataSource() {
165166

166167
private Properties quartzProperties;
167168

168-
169169
private Executor taskExecutor;
170170

171171
private DataSource dataSource;
172172

173173
private DataSource nonTransactionalDataSource;
174174

175-
176175
private Map<String, ?> schedulerContextMap;
177176

178177
private ApplicationContext applicationContext;
@@ -183,7 +182,6 @@ public static DataSource getConfigTimeNonTransactionalDataSource() {
183182

184183
private boolean jobFactorySet = false;
185184

186-
187185
private boolean autoStartup = true;
188186

189187
private int startupDelay = 0;
@@ -194,19 +192,38 @@ public static DataSource getConfigTimeNonTransactionalDataSource() {
194192

195193
private boolean waitForJobsToCompleteOnShutdown = false;
196194

197-
198195
private Scheduler scheduler;
199196

200197

201198
/**
202-
* Set the Quartz SchedulerFactory implementation to use.
203-
* <p>Default is {@link StdSchedulerFactory}, reading in the standard
204-
* {@code quartz.properties} from {@code quartz.jar}.
205-
* To use custom Quartz properties, specify the "configLocation"
206-
* or "quartzProperties" bean property on this FactoryBean.
199+
* Set an external Quartz {@link SchedulerFactory} instance to use.
200+
* <p>Default is an internal {@link StdSchedulerFactory} instance. If this method is
201+
* called, it overrides any class specified through {@link #setSchedulerFactoryClass}
202+
* as well as any settings specified through {@link #setConfigLocation},
203+
* {@link #setQuartzProperties}, {@link #setTaskExecutor} or {@link #setDataSource}.
204+
* <p><b>NOTE:</b> With an externally provided {@code SchedulerFactory} instance,
205+
* local settings such as {@link #setConfigLocation} or {@link #setQuartzProperties}
206+
* will be ignored here in {@code SchedulerFactoryBean}, expecting the external
207+
* {@code SchedulerFactory} instance to get initialized on its own.
208+
* @since 4.3.15
209+
* @see #setSchedulerFactoryClass
210+
*/
211+
public void setSchedulerFactory(SchedulerFactory schedulerFactory) {
212+
this.schedulerFactory = schedulerFactory;
213+
}
214+
215+
/**
216+
* Set the Quartz {@link SchedulerFactory} implementation to use.
217+
* <p>Default is the {@link StdSchedulerFactory} class, reading in the standard
218+
* {@code quartz.properties} from {@code quartz.jar}. For applying custom Quartz
219+
* properties, specify {@link #setConfigLocation "configLocation"} and/or
220+
* {@link #setQuartzProperties "quartzProperties"} etc on this local
221+
* {@code SchedulerFactoryBean} instance.
207222
* @see org.quartz.impl.StdSchedulerFactory
208223
* @see #setConfigLocation
209224
* @see #setQuartzProperties
225+
* @see #setTaskExecutor
226+
* @see #setDataSource
210227
*/
211228
public void setSchedulerFactoryClass(Class<? extends SchedulerFactory> schedulerFactoryClass) {
212229
this.schedulerFactoryClass = schedulerFactoryClass;
@@ -244,14 +261,14 @@ public void setQuartzProperties(Properties quartzProperties) {
244261
this.quartzProperties = quartzProperties;
245262
}
246263

247-
248264
/**
249-
* Set the Spring TaskExecutor to use as Quartz backend.
265+
* Set a Spring-managed {@link Executor} to use as Quartz backend.
250266
* Exposed as thread pool through the Quartz SPI.
251-
* <p>Can be used to assign a JDK 1.5 ThreadPoolExecutor or a CommonJ
267+
* <p>Can be used to assign a local JDK ThreadPoolExecutor or a CommonJ
252268
* WorkManager as Quartz backend, to avoid Quartz's manual thread creation.
253269
* <p>By default, a Quartz SimpleThreadPool will be used, configured through
254270
* the corresponding Quartz properties.
271+
* @since 2.0
255272
* @see #setQuartzProperties
256273
* @see LocalTaskExecutorThreadPool
257274
* @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
@@ -262,8 +279,8 @@ public void setTaskExecutor(Executor taskExecutor) {
262279
}
263280

264281
/**
265-
* Set the default DataSource to be used by the Scheduler. If set,
266-
* this will override corresponding settings in Quartz properties.
282+
* Set the default {@link DataSource} to be used by the Scheduler.
283+
* If set, this will override corresponding settings in Quartz properties.
267284
* <p>Note: If this is set, the Quartz settings should not define
268285
* a job store "dataSource" to avoid meaningless double configuration.
269286
* <p>A Spring-specific subclass of Quartz' JobStoreCMT will be used.
@@ -276,6 +293,7 @@ public void setTaskExecutor(Executor taskExecutor) {
276293
* argument is sufficient. In case of an XA DataSource and global JTA transactions,
277294
* SchedulerFactoryBean's "nonTransactionalDataSource" property should be set,
278295
* passing in a non-XA DataSource that will not participate in global transactions.
296+
* @since 1.1
279297
* @see #setNonTransactionalDataSource
280298
* @see #setQuartzProperties
281299
* @see #setTransactionManager
@@ -286,20 +304,20 @@ public void setDataSource(DataSource dataSource) {
286304
}
287305

288306
/**
289-
* Set the DataSource to be used by the Scheduler <i>for non-transactional access</i>.
307+
* Set the {@link DataSource} to be used <i>for non-transactional access</i>.
290308
* <p>This is only necessary if the default DataSource is an XA DataSource that will
291309
* always participate in transactions: A non-XA version of that DataSource should
292310
* be specified as "nonTransactionalDataSource" in such a scenario.
293311
* <p>This is not relevant with a local DataSource instance and Spring transactions.
294312
* Specifying a single default DataSource as "dataSource" is sufficient there.
313+
* @since 1.1
295314
* @see #setDataSource
296315
* @see LocalDataSourceJobStore
297316
*/
298317
public void setNonTransactionalDataSource(DataSource nonTransactionalDataSource) {
299318
this.nonTransactionalDataSource = nonTransactionalDataSource;
300319
}
301320

302-
303321
/**
304322
* Register objects in the Scheduler context via a given Map.
305323
* These objects will be available to any Job that runs in this Scheduler.
@@ -315,7 +333,7 @@ public void setSchedulerContextAsMap(Map<String, ?> schedulerContextAsMap) {
315333
}
316334

317335
/**
318-
* Set the key of an ApplicationContext reference to expose in the
336+
* Set the key of an {@link ApplicationContext} reference to expose in the
319337
* SchedulerContext, for example "applicationContext". Default is none.
320338
* Only applicable when running in a Spring ApplicationContext.
321339
* <p>Note: When using persistent Jobs whose JobDetail will be kept in the
@@ -335,7 +353,7 @@ public void setApplicationContextSchedulerContextKey(String applicationContextSc
335353
}
336354

337355
/**
338-
* Set the Quartz JobFactory to use for this Scheduler.
356+
* Set the Quartz {@link JobFactory} to use for this Scheduler.
339357
* <p>Default is Spring's {@link AdaptableJobFactory}, which supports
340358
* {@link java.lang.Runnable} objects as well as standard Quartz
341359
* {@link org.quartz.Job} instances. Note that this default only applies
@@ -344,6 +362,7 @@ public void setApplicationContextSchedulerContextKey(String applicationContextSc
344362
* <p>Specify an instance of Spring's {@link SpringBeanJobFactory} here
345363
* (typically as an inner bean definition) to automatically populate a job's
346364
* bean properties from the specified job data map and scheduler context.
365+
* @since 2.0
347366
* @see AdaptableJobFactory
348367
* @see SpringBeanJobFactory
349368
*/
@@ -352,7 +371,6 @@ public void setJobFactory(JobFactory jobFactory) {
352371
this.jobFactorySet = true;
353372
}
354373

355-
356374
/**
357375
* Set whether to automatically start the scheduler after initialization.
358376
* <p>Default is "true"; set this to "false" to allow for manual startup.
@@ -372,11 +390,12 @@ public boolean isAutoStartup() {
372390
}
373391

374392
/**
375-
* Specify the phase in which this scheduler should be started and
376-
* stopped. The startup order proceeds from lowest to highest, and
377-
* the shutdown order is the reverse of that. By default this value
378-
* is Integer.MAX_VALUE meaning that this scheduler starts as late
379-
* as possible and stops as soon as possible.
393+
* Specify the phase in which this scheduler should be started and stopped.
394+
* The startup order proceeds from lowest to highest, and the shutdown order
395+
* is the reverse of that. By default this value is {@code Integer.MAX_VALUE}
396+
* meaning that this scheduler starts as late as possible and stops as soon
397+
* as possible.
398+
* @since 3.0
380399
*/
381400
public void setPhase(int phase) {
382401
this.phase = phase;
@@ -424,7 +443,6 @@ public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnSh
424443
this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
425444
}
426445

427-
428446
@Override
429447
public void setBeanName(String name) {
430448
if (this.schedulerName == null) {
@@ -452,9 +470,8 @@ public void afterPropertiesSet() throws Exception {
452470
this.resourceLoader = this.applicationContext;
453471
}
454472

455-
// Create SchedulerFactory instance...
456-
SchedulerFactory schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
457-
initSchedulerFactory(schedulerFactory);
473+
// Initialize the SchedulerFactory instance...
474+
SchedulerFactory schedulerFactory = prepareSchedulerFactory();
458475

459476
if (this.resourceLoader != null) {
460477
// Make given ResourceLoader available for SchedulerFactory configuration.
@@ -512,22 +529,34 @@ public void afterPropertiesSet() throws Exception {
512529

513530

514531
/**
515-
* Load and/or apply Quartz properties to the given SchedulerFactory.
516-
* @param schedulerFactory the SchedulerFactory to initialize
532+
* Create a SchedulerFactory if necessary and apply locally defined Quartz properties to it.
533+
* @return the initialized SchedulerFactory
517534
*/
518-
private void initSchedulerFactory(SchedulerFactory schedulerFactory) throws SchedulerException, IOException {
519-
if (!(schedulerFactory instanceof StdSchedulerFactory)) {
520-
if (this.configLocation != null || this.quartzProperties != null ||
535+
private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException {
536+
SchedulerFactory schedulerFactory = this.schedulerFactory;
537+
if (schedulerFactory == null) {
538+
// Create local SchedulerFactory instance (typically a StdSchedulerFactory)
539+
schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass);
540+
if (schedulerFactory instanceof StdSchedulerFactory) {
541+
initSchedulerFactory((StdSchedulerFactory) schedulerFactory);
542+
}
543+
else if (this.configLocation != null || this.quartzProperties != null ||
521544
this.taskExecutor != null || this.dataSource != null) {
522545
throw new IllegalArgumentException(
523546
"StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory);
524547
}
525-
// Otherwise assume that no initialization is necessary...
526-
return;
548+
// Otherwise, no local settings to be applied via StdSchedulerFactory.initialize(Properties)
527549
}
550+
// Otherwise, assume that externally provided factory has been initialized with appropriate settings
551+
return schedulerFactory;
552+
}
528553

554+
/**
555+
* Initialize the given SchedulerFactory, applying locally defined Quartz properties to it.
556+
* @param schedulerFactory the SchedulerFactory to initialize
557+
*/
558+
private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws SchedulerException, IOException {
529559
Properties mergedProps = new Properties();
530-
531560
if (this.resourceLoader != null) {
532561
mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_CLASS_LOAD_HELPER_CLASS,
533562
ResourceLoaderClassLoadHelper.class.getName());
@@ -552,17 +581,14 @@ private void initSchedulerFactory(SchedulerFactory schedulerFactory) throws Sche
552581
}
553582

554583
CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps);
555-
556584
if (this.dataSource != null) {
557585
mergedProps.put(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName());
558586
}
559-
560-
// Make sure to set the scheduler name as configured in the Spring configuration.
561587
if (this.schedulerName != null) {
562588
mergedProps.put(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName);
563589
}
564590

565-
((StdSchedulerFactory) schedulerFactory).initialize(mergedProps);
591+
schedulerFactory.initialize(mergedProps);
566592
}
567593

568594
/**
@@ -619,7 +645,7 @@ protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String sc
619645
private void populateSchedulerContext() throws SchedulerException {
620646
// Put specified objects into Scheduler context.
621647
if (this.schedulerContextMap != null) {
622-
this.scheduler.getContext().putAll(this.schedulerContextMap);
648+
getScheduler().getContext().putAll(this.schedulerContextMap);
623649
}
624650

625651
// Register ApplicationContext in Scheduler context.
@@ -629,7 +655,7 @@ private void populateSchedulerContext() throws SchedulerException {
629655
"SchedulerFactoryBean needs to be set up in an ApplicationContext " +
630656
"to be able to handle an 'applicationContextSchedulerContextKey'");
631657
}
632-
this.scheduler.getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext);
658+
getScheduler().getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext);
633659
}
634660
}
635661

@@ -659,6 +685,7 @@ public void run() {
659685
Thread.sleep(startupDelay * 1000);
660686
}
661687
catch (InterruptedException ex) {
688+
Thread.currentThread().interrupt();
662689
// simply proceed
663690
}
664691
if (logger.isInfoEnabled()) {
@@ -762,8 +789,10 @@ public boolean isRunning() throws SchedulingException {
762789
*/
763790
@Override
764791
public void destroy() throws SchedulerException {
765-
logger.info("Shutting down Quartz Scheduler");
766-
this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown);
792+
if (this.scheduler != null) {
793+
logger.info("Shutting down Quartz Scheduler");
794+
this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown);
795+
}
767796
}
768797

769798
}

0 commit comments

Comments
 (0)