From 9d04ce24289507ba79f56142d06b287136609d00 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Mon, 26 Oct 2015 01:29:56 +0100 Subject: [PATCH] Add Quartz Scheduler support --- spring-boot-autoconfigure/pom.xml | 5 + .../quartz/AutowireCapableBeanJobFactory.java | 50 ++++ .../quartz/QuartzAutoConfiguration.java | 161 ++++++++++++ .../quartz/QuartzDatabaseInitializer.java | 70 ++++++ .../quartz/QuartzProperties.java | 86 +++++++ .../SchedulerFactoryBeanCustomizer.java | 33 +++ .../autoconfigure/quartz/package-info.java | 20 ++ .../main/resources/META-INF/spring.factories | 1 + .../quartz/QuartzAutoConfigurationTests.java | 230 ++++++++++++++++++ spring-boot-dependencies/pom.xml | 11 + .../appendix-application-properties.adoc | 5 + spring-boot-docs/src/main/asciidoc/index.adoc | 2 +- .../main/asciidoc/spring-boot-features.adoc | 31 +++ spring-boot-samples/pom.xml | 1 + .../spring-boot-sample-quartz/README.adoc | 11 + .../spring-boot-sample-quartz/pom.xml | 35 +++ .../main/java/sample/quartz/SampleJob.java | 20 ++ .../quartz/SampleQuartzApplication.java | 35 +++ spring-boot-starters/pom.xml | 1 + .../spring-boot-starter-quartz/pom.xml | 38 +++ .../main/resources/META-INF/spring.provides | 1 + 21 files changed, 846 insertions(+), 1 deletion(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/AutowireCapableBeanJobFactory.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzDatabaseInitializer.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzProperties.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerFactoryBeanCustomizer.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/package-info.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-quartz/README.adoc create mode 100644 spring-boot-samples/spring-boot-sample-quartz/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleJob.java create mode 100644 spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java create mode 100644 spring-boot-starters/spring-boot-starter-quartz/pom.xml create mode 100644 spring-boot-starters/spring-boot-starter-quartz/src/main/resources/META-INF/spring.provides diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 2126c5c248e2..1b7893a057ed 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -656,6 +656,11 @@ narayana-jts-integration true + + org.quartz-scheduler + quartz + true + org.springframework.boot diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/AutowireCapableBeanJobFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/AutowireCapableBeanJobFactory.java new file mode 100644 index 000000000000..36828976da0a --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/AutowireCapableBeanJobFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.quartz; + +import org.quartz.spi.TriggerFiredBundle; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.scheduling.quartz.SpringBeanJobFactory; +import org.springframework.util.Assert; + +/** + * Subclass of {@link SpringBeanJobFactory} that supports auto-wiring job beans. + * + * @author Vedran Pavic + * @since 2.0.0 + * @see Inject application + * context dependencies in Quartz job beans + */ +class AutowireCapableBeanJobFactory extends SpringBeanJobFactory { + + private final AutowireCapableBeanFactory beanFactory; + + AutowireCapableBeanJobFactory(AutowireCapableBeanFactory beanFactory) { + Assert.notNull(beanFactory, "Bean factory must not be null"); + this.beanFactory = beanFactory; + } + + @Override + protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { + Object jobInstance = super.createJobInstance(bundle); + this.beanFactory.autowireBean(jobInstance); + this.beanFactory.initializeBean(jobInstance, null); + return jobInstance; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java new file mode 100644 index 000000000000..1748a4e3d805 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java @@ -0,0 +1,161 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.quartz; + +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; + +import javax.sql.DataSource; + +import org.quartz.Calendar; +import org.quartz.JobDetail; +import org.quartz.Scheduler; +import org.quartz.Trigger; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.io.ResourceLoader; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Quartz Scheduler. + * + * @author Vedran Pavic + * @since 2.0.0 + */ +@Configuration +@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, + PlatformTransactionManager.class }) +@EnableConfigurationProperties(QuartzProperties.class) +@AutoConfigureAfter({ DataSourceAutoConfiguration.class, + HibernateJpaAutoConfiguration.class }) +public class QuartzAutoConfiguration implements ApplicationContextAware { + + private final QuartzProperties properties; + + private final List customizers; + + private final Executor taskExecutor; + + private final JobDetail[] jobDetails; + + private final Map calendars; + + private final Trigger[] triggers; + + private ApplicationContext applicationContext; + + public QuartzAutoConfiguration(QuartzProperties properties, + ObjectProvider> customizers, + ObjectProvider taskExecutor, ObjectProvider jobDetails, + ObjectProvider> calendars, + ObjectProvider triggers) { + this.properties = properties; + this.customizers = customizers.getIfAvailable(); + this.taskExecutor = taskExecutor.getIfAvailable(); + this.jobDetails = jobDetails.getIfAvailable(); + this.calendars = calendars.getIfAvailable(); + this.triggers = triggers.getIfAvailable(); + } + + @Bean + @ConditionalOnBean(DataSource.class) + @ConditionalOnMissingBean + public QuartzDatabaseInitializer quartzDatabaseInitializer(DataSource dataSource, + ResourceLoader resourceLoader) { + return new QuartzDatabaseInitializer(dataSource, resourceLoader, this.properties); + } + + @Bean + @ConditionalOnMissingBean + public SchedulerFactoryBean schedulerFactoryBean() { + SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); + schedulerFactoryBean.setJobFactory(new AutowireCapableBeanJobFactory( + this.applicationContext.getAutowireCapableBeanFactory())); + if (!this.properties.getProperties().isEmpty()) { + schedulerFactoryBean + .setQuartzProperties(asProperties(this.properties.getProperties())); + } + if (this.taskExecutor != null) { + schedulerFactoryBean.setTaskExecutor(this.taskExecutor); + } + if (this.jobDetails != null && this.jobDetails.length > 0) { + schedulerFactoryBean.setJobDetails(this.jobDetails); + } + if (this.calendars != null && !this.calendars.isEmpty()) { + schedulerFactoryBean.setCalendars(this.calendars); + } + if (this.triggers != null && this.triggers.length > 0) { + schedulerFactoryBean.setTriggers(this.triggers); + } + customize(schedulerFactoryBean); + return schedulerFactoryBean; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } + + private Properties asProperties(Map source) { + Properties properties = new Properties(); + properties.putAll(source); + return properties; + } + + private void customize(SchedulerFactoryBean schedulerFactoryBean) { + if (this.customizers != null) { + AnnotationAwareOrderComparator.sort(this.customizers); + for (SchedulerFactoryBeanCustomizer customizer : this.customizers) { + customizer.customize(schedulerFactoryBean); + } + } + } + + @Configuration + @ConditionalOnBean(DataSource.class) + protected static class QuartzSchedulerDataSourceConfiguration { + + @Bean + public SchedulerFactoryBeanCustomizer dataSourceCustomizer(DataSource dataSource, + PlatformTransactionManager transactionManager) { + return schedulerFactoryBean -> { + schedulerFactoryBean.setDataSource(dataSource); + schedulerFactoryBean.setTransactionManager(transactionManager); + }; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzDatabaseInitializer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzDatabaseInitializer.java new file mode 100644 index 000000000000..38d3b290056c --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzDatabaseInitializer.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.quartz; + +import javax.sql.DataSource; + +import org.springframework.boot.autoconfigure.AbstractDatabaseInitializer; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.Assert; + +/** + * Initializer for Quartz Scheduler schema. + * + * @author Vedran Pavic + * @since 2.0.0 + */ +public class QuartzDatabaseInitializer extends AbstractDatabaseInitializer { + + private final QuartzProperties properties; + + public QuartzDatabaseInitializer(DataSource dataSource, ResourceLoader resourceLoader, + QuartzProperties properties) { + super(dataSource, resourceLoader); + Assert.notNull(properties, "QuartzProperties must not be null"); + this.properties = properties; + } + + @Override + protected boolean isEnabled() { + return this.properties.getInitializer().isEnabled(); + } + + @Override + protected String getSchemaLocation() { + return this.properties.getSchema(); + } + + @Override + protected String getDatabaseName() { + String databaseName = super.getDatabaseName(); + if ("db2".equals(databaseName)) { + return "db2_v95"; + } + if ("mysql".equals(databaseName)) { + return "mysql_innodb"; + } + if ("postgresql".equals(databaseName)) { + return "postgres"; + } + if ("sqlserver".equals(databaseName)) { + return "sqlServer"; + } + return databaseName; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzProperties.java new file mode 100644 index 000000000000..7570e01a35d3 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzProperties.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.quartz; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for the Quartz Scheduler integration. + * + * @author Vedran Pavic + * @since 2.0.0 + */ +@ConfigurationProperties("spring.quartz") +public class QuartzProperties { + + private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/quartz/impl/" + + "jdbcjobstore/tables_@@platform@@.sql"; + + private final Initializer initializer = new Initializer(); + + /** + * Additional Quartz Scheduler properties. + */ + private Map properties = new HashMap<>(); + + /** + * Path to the SQL file to use to initialize the database schema. + */ + private String schema = DEFAULT_SCHEMA_LOCATION; + + public Initializer getInitializer() { + return this.initializer; + } + + public Map getProperties() { + return this.properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public String getSchema() { + return this.schema; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public class Initializer { + + /** + * Create the required Quartz Scheduler tables on startup if necessary. Enabled + * automatically if the schema is configured. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled && QuartzProperties.this.getSchema() != null; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerFactoryBeanCustomizer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerFactoryBeanCustomizer.java new file mode 100644 index 000000000000..09a7b2fa2eb7 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerFactoryBeanCustomizer.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.quartz; + +import org.springframework.scheduling.quartz.SchedulerFactoryBean; + +/** + * Callback interface that can be implemented by beans wishing to customize the Quartz + * {@link SchedulerFactoryBean} before it is fully initialized, in particular to tune its + * configuration. + * + * @author Vedran Pavic + * @since 2.0.0 + */ +public interface SchedulerFactoryBeanCustomizer { + + void customize(SchedulerFactoryBean schedulerFactoryBean); + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/package-info.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/package-info.java new file mode 100644 index 000000000000..ddfdec58970d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for Quartz Scheduler. + */ +package org.springframework.boot.autoconfigure.quartz; diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 14474fb5bd26..8d3ba7d5a8d6 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -92,6 +92,7 @@ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ +org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\ org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java new file mode 100644 index 000000000000..17f21fbfc0a9 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java @@ -0,0 +1,230 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.quartz; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.quartz.Calendar; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SimpleScheduleBuilder; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import org.quartz.impl.calendar.MonthlyCalendar; +import org.quartz.impl.calendar.WeeklyCalendar; +import org.quartz.simpl.RAMJobStore; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.test.rule.OutputCapture; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.scheduling.quartz.LocalDataSourceJobStore; +import org.springframework.scheduling.quartz.LocalTaskExecutorThreadPool; +import org.springframework.scheduling.quartz.QuartzJobBean; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.containsString; + +/** + * Tests for {@link QuartzAutoConfiguration}. + * + * @author Vedran Pavic + */ +public class QuartzAutoConfigurationTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Rule + public OutputCapture output = new OutputCapture(); + + private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @After + public void closeContext() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void withDatabase() throws Exception { + registerAndRefresh(EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + QuartzAutoConfiguration.class); + Scheduler scheduler = this.context.getBean(Scheduler.class); + + assertThat(scheduler).isNotNull(); + assertThat(scheduler.getMetaData().getJobStoreClass()) + .isAssignableFrom(LocalDataSourceJobStore.class); + } + + @Test + public void withNoDatabase() throws Exception { + registerAndRefresh(QuartzAutoConfiguration.class); + Scheduler scheduler = this.context.getBean(Scheduler.class); + + assertThat(scheduler).isNotNull(); + assertThat(scheduler.getMetaData().getJobStoreClass()) + .isAssignableFrom(RAMJobStore.class); + } + + @Test + public void withTaskExecutor() throws Exception { + registerAndRefresh(QuartzAutoConfiguration.class, + QuartzExecutorConfiguration.class); + Scheduler scheduler = this.context.getBean(Scheduler.class); + + assertThat(scheduler).isNotNull(); + assertThat(scheduler.getMetaData().getThreadPoolClass()) + .isEqualTo(LocalTaskExecutorThreadPool.class); + } + + @Test + public void withConfiguredJobAndTrigger() throws Exception { + TestPropertyValues.of("test-name=withConfiguredJobAndTrigger") + .applyTo(this.context); + registerAndRefresh(QuartzAutoConfiguration.class, QuartzJobConfiguration.class); + Scheduler scheduler = this.context.getBean(Scheduler.class); + + assertThat(scheduler.getJobDetail(JobKey.jobKey("fooJob"))).isNotNull(); + assertThat(scheduler.getTrigger(TriggerKey.triggerKey("fooTrigger"))).isNotNull(); + Thread.sleep(1000L); + this.output.expect(containsString("withConfiguredJobAndTrigger")); + } + + @Test + public void withConfiguredCalendars() throws Exception { + registerAndRefresh(QuartzAutoConfiguration.class, + QuartzCalendarsConfiguration.class); + Scheduler scheduler = this.context.getBean(Scheduler.class); + + assertThat(scheduler.getCalendar("weekly")).isNotNull(); + assertThat(scheduler.getCalendar("monthly")).isNotNull(); + } + + @Test + public void withQuartzProperties() throws Exception { + TestPropertyValues + .of("spring.quartz.properties.org.quartz.scheduler.instanceId=FOO") + .applyTo(this.context); + registerAndRefresh(QuartzAutoConfiguration.class); + Scheduler scheduler = this.context.getBean(Scheduler.class); + + assertThat(scheduler).isNotNull(); + assertThat(scheduler.getSchedulerInstanceId()).isEqualTo("FOO"); + } + + @Test + public void withCustomizer() throws Exception { + registerAndRefresh(QuartzAutoConfiguration.class, QuartzCustomConfig.class); + Scheduler scheduler = this.context.getBean(Scheduler.class); + + assertThat(scheduler).isNotNull(); + assertThat(scheduler.getSchedulerName()).isEqualTo("fooScheduler"); + } + + private void registerAndRefresh(Class... annotatedClasses) { + this.context.register(annotatedClasses); + this.context.refresh(); + } + + @Configuration + protected static class QuartzJobConfiguration { + + @Bean + public JobDetail fooJob() { + return JobBuilder.newJob().ofType(FooJob.class).withIdentity("fooJob") + .storeDurably().build(); + } + + @Bean + public Trigger fooTrigger() { + SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() + .withIntervalInSeconds(10).repeatForever(); + + return TriggerBuilder.newTrigger().forJob(fooJob()).withIdentity("fooTrigger") + .withSchedule(scheduleBuilder).build(); + } + + } + + @Configuration + protected static class QuartzCalendarsConfiguration { + + @Bean + public Calendar weekly() { + return new WeeklyCalendar(); + } + + @Bean + public Calendar monthly() { + return new MonthlyCalendar(); + } + + } + + @Configuration + protected static class QuartzExecutorConfiguration { + + @Bean + public Executor executor() { + return Executors.newSingleThreadExecutor(); + } + + } + + @Configuration + protected static class QuartzCustomConfig { + + @Bean + public SchedulerFactoryBeanCustomizer customizer() { + return schedulerFactoryBean -> schedulerFactoryBean + .setSchedulerName("fooScheduler"); + } + + } + + public static class FooJob extends QuartzJobBean { + + @Autowired + private Environment env; + + @Override + protected void executeInternal(JobExecutionContext context) + throws JobExecutionException { + System.out.println(this.env.getProperty("test-name", "unknown")); + } + + } + +} diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 517ae142dd48..b9280d5a756a 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -138,6 +138,7 @@ 3.0.0-M01 4.1.11.Final 42.1.1 + 2.3.0 4.1.4 Bismuth-M1 1.3.0 @@ -491,6 +492,11 @@ spring-boot-starter-reactor-netty 2.0.0.BUILD-SNAPSHOT + + org.springframework.boot + spring-boot-starter-quartz + 2.0.0.BUILD-SNAPSHOT + org.springframework.boot spring-boot-starter-security @@ -1970,6 +1976,11 @@ lombok ${lombok.version} + + org.quartz-scheduler + quartz + ${quartz.version} + org.seleniumhq.selenium htmlunit-driver diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index d8b95cc15dfa..2f9e1e4b18f3 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -129,6 +129,11 @@ content into your application; rather pick only the properties that you need. spring.profiles.active= # Comma-separated list (or list if using YAML) of <>. spring.profiles.include= # Unconditionally activate the specified comma separated profiles (or list of profiles if using YAML). + # QUARTZ SCHEDULER ({sc-spring-boot-autoconfigure}/quartz/QuartzProperties.{sc-ext}[QuartzProperties]) + spring.quartz.initializer.enabled=true # Create the required Quartz Scheduler tables on startup if necessary. Enabled automatically if the schema is configured. + spring.quartz.properties.*= # Additional Quartz Scheduler properties. + spring.quartz.schema=classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql # Path to the SQL file to use to initialize the database schema. + # Reactor spring.reactor.stacktrace-mode.enabled=false # Set whether Reactor should collect stacktrace information at runtime. diff --git a/spring-boot-docs/src/main/asciidoc/index.adoc b/spring-boot-docs/src/main/asciidoc/index.adoc index 21a401598bdb..2672eda02962 100644 --- a/spring-boot-docs/src/main/asciidoc/index.adoc +++ b/spring-boot-docs/src/main/asciidoc/index.adoc @@ -1,5 +1,5 @@ = Spring Boot Reference Guide -Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; Andy Wilkinson; Marcel Overdijk; Christian Dupuis; Sébastien Deleuze; Michael Simons +Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; Andy Wilkinson; Marcel Overdijk; Christian Dupuis; Sébastien Deleuze; Michael Simons; Vedran Pavić :doctype: book :toc: :toclevels: 4 diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 76974087b4e3..aa1a3daf66f0 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -5151,6 +5151,37 @@ caching is enabled. +[[boot-features-quartz]] +== Quartz Scheduler + +If Quartz Scheduler and the relevant libraries (as defined by `spring-boot-starter-quartz`) +are on the classpath, Spring Boot will auto-configure a `SchedulerFactoryBean` which +provides `Scheduler` instance that you can inject in your application. + +Beans of following types will be automatically picked up and added to +`SchedulerFactoryBean`: + +* `JobDetail` +* `Calendar` +* `Trigger` + +By default, an in-memory `JobStore` will be used. However, if `DataSource` bean is +available in your application, Quartz Scheduler will be configured with a persistent +`JobStore`. + +When using a persistent `JobStore`, Quartz database schema can be initialized using +`QuartzDatabaseInitializer` if the location of schema script is configured using +`spring.quartz.schema` property. + +Quartz Scheduler configuration can also be customized using Quartz configuration properties +(see `spring.quartz.properties.*`) and `SchedulerFactoryBeanCustomizer` beans which +allows programmatic `SchedulerFactoryBean` customization. + +Spring Boot also configures `JobFactory` that is `@Autowire` capable so you can easily +inject beans from `applicationContext` and use them in your Quartz jobs. + + + [[boot-features-integration]] == Spring Integration Spring Boot offers several conveniences for working with Spring Integration, including diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index 7a263b099410..8fa1bc4280ea 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -71,6 +71,7 @@ spring-boot-sample-parent-context spring-boot-sample-profile spring-boot-sample-property-validation + spring-boot-sample-quartz spring-boot-sample-secure spring-boot-sample-secure-oauth2 spring-boot-sample-secure-oauth2-actuator diff --git a/spring-boot-samples/spring-boot-sample-quartz/README.adoc b/spring-boot-samples/spring-boot-sample-quartz/README.adoc new file mode 100644 index 000000000000..87e93024c823 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-quartz/README.adoc @@ -0,0 +1,11 @@ +== Spring Boot Quartz Sample + +This sample demonstrates the Quartz auto-configuration support. + +The sample uses Maven. It can be built and run from the command line: + +---- +$ mvn spring-boot:run +---- + +Console log will now show Hello message from SampleJob every 10 seconds. diff --git a/spring-boot-samples/spring-boot-sample-quartz/pom.xml b/spring-boot-samples/spring-boot-sample-quartz/pom.xml new file mode 100644 index 000000000000..a161afa0be62 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-quartz/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 2.0.0.BUILD-SNAPSHOT + + spring-boot-sample-quartz + Spring Boot Quartz Sample + Spring Boot Quartz Sample + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter-quartz + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleJob.java b/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleJob.java new file mode 100644 index 000000000000..f5d82888836e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleJob.java @@ -0,0 +1,20 @@ +package sample.quartz; + +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.scheduling.quartz.QuartzJobBean; + +public class SampleJob extends QuartzJobBean { + + private static final Logger LOGGER = LoggerFactory.getLogger(SampleJob.class); + + @Override + protected void executeInternal(JobExecutionContext context) + throws JobExecutionException { + LOGGER.info("Hello {}!", context.getJobDetail().getKey()); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java b/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java new file mode 100644 index 000000000000..6c1530071fe0 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java @@ -0,0 +1,35 @@ +package sample.quartz; + +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.SimpleScheduleBuilder; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class SampleQuartzApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleQuartzApplication.class, args); + } + + @Bean + public JobDetail jobDetail() { + return JobBuilder.newJob().ofType(SampleJob.class).withIdentity("sampleJob") + .storeDurably().build(); + } + + @Bean + public Trigger trigger() { + SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() + .withIntervalInSeconds(10).repeatForever(); + + return TriggerBuilder.newTrigger().forJob(jobDetail()) + .withIdentity("sampleTrigger").withSchedule(scheduleBuilder).build(); + } + +} diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index eaff2e49abfd..ddf430ce2528 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -59,6 +59,7 @@ spring-boot-starter-mustache spring-boot-starter-actuator spring-boot-starter-parent + spring-boot-starter-quartz spring-boot-starter-reactor-netty spring-boot-starter-security spring-boot-starter-social-facebook diff --git a/spring-boot-starters/spring-boot-starter-quartz/pom.xml b/spring-boot-starters/spring-boot-starter-quartz/pom.xml new file mode 100644 index 000000000000..a2f0c7489f57 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-quartz/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starters + 2.0.0.BUILD-SNAPSHOT + + spring-boot-starter-quartz + Spring Boot Quartz Starter + Spring Boot Quartz Starter + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter + + + org.springframework + spring-context-support + + + org.springframework + spring-tx + + + org.quartz-scheduler + quartz + + + diff --git a/spring-boot-starters/spring-boot-starter-quartz/src/main/resources/META-INF/spring.provides b/spring-boot-starters/spring-boot-starter-quartz/src/main/resources/META-INF/spring.provides new file mode 100644 index 000000000000..55020b99f9d8 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-quartz/src/main/resources/META-INF/spring.provides @@ -0,0 +1 @@ +provides: spring-context-support,spring-tx,quartz