From 7e5bd1f281c74e1dfb69a2b2718b10bb7400a265 Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Tue, 16 Jul 2019 22:41:00 +0300 Subject: [PATCH] Ensure Flyway/Liquibase runs before Quartz Add post processors to ensure that SchedulerFactoryBean and Scheduler beans depend on the Flyway and Liquibase beans. See gh-17539 --- .../quartz/QuartzAutoConfiguration.java | 74 +++++++++++++++++-- .../quartz/QuartzAutoConfigurationTests.java | 36 +++++++++ 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java index 67d35d2779..0f7a4928b1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java @@ -21,20 +21,27 @@ import java.util.Properties; import javax.sql.DataSource; +import liquibase.integration.spring.SpringLiquibase; +import org.flywaydb.core.Flyway; import org.quartz.Calendar; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.Trigger; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor; 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.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; +import org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializer; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; @@ -56,7 +63,8 @@ import org.springframework.transaction.PlatformTransactionManager; @Configuration @ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class }) @EnableConfigurationProperties(QuartzProperties.class) -@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) +@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, + LiquibaseAutoConfiguration.class, FlywayAutoConfiguration.class }) public class QuartzAutoConfiguration { private final QuartzProperties properties; @@ -157,16 +165,68 @@ public class QuartzAutoConfiguration { return new QuartzDataSourceInitializer(dataSourceToUse, resourceLoader, properties); } - @Bean - public static DataSourceInitializerSchedulerDependencyPostProcessor dataSourceInitializerSchedulerDependencyPostProcessor() { - return new DataSourceInitializerSchedulerDependencyPostProcessor(); + /** + * Additional configuration to ensure that {@link SchedulerFactoryBean} and + * {@link Scheduler} beans depend on the {@link QuartzDataSourceInitializer} + * bean(s). + */ + @Configuration + protected static class SchedulerQuartzDataSourceInitializerDependencyConfiguration + extends AbstractSchedulerDependsOnBeanFactoryPostProcessor { + + SchedulerQuartzDataSourceInitializerDependencyConfiguration() { + super(QuartzDataSourceInitializer.class); + } + + } + + /** + * Additional configuration to ensure that {@link SchedulerFactoryBean} and + * {@link Scheduler} beans depend on the {@link SpringLiquibase} bean(s). + */ + @Configuration + @ConditionalOnClass(SpringLiquibase.class) + @ConditionalOnBean(SpringLiquibase.class) + protected static class SchedulerSpringLiquibaseDependencyConfiguration + extends AbstractSchedulerDependsOnBeanFactoryPostProcessor { + + SchedulerSpringLiquibaseDependencyConfiguration() { + super(SpringLiquibase.class); + } + + } + + /** + * Additional configuration to ensure that {@link SchedulerFactoryBean} and + * {@link Scheduler} beans depend on the {@link FlywayMigrationInitializer} + * bean(s). + */ + @Configuration + @ConditionalOnClass(Flyway.class) + @ConditionalOnBean(FlywayMigrationInitializer.class) + protected static class SchedulerFlywayMigrationInitializerDependencyConfiguration + extends AbstractSchedulerDependsOnBeanFactoryPostProcessor { + + SchedulerFlywayMigrationInitializerDependencyConfiguration() { + super(FlywayMigrationInitializer.class); + } + } - private static class DataSourceInitializerSchedulerDependencyPostProcessor + /** + * {@link BeanFactoryPostProcessor} that can be used to declare that all + * {@link Scheduler} and {@link SchedulerFactoryBean} beans should "depend on" one + * or more specific beans. + */ + protected abstract static class AbstractSchedulerDependsOnBeanFactoryPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor { - DataSourceInitializerSchedulerDependencyPostProcessor() { - super(Scheduler.class, SchedulerFactoryBean.class, "quartzDataSourceInitializer"); + /** + * Create an instance with dependency types. + * @param dependencyTypes dependency types + */ + protected AbstractSchedulerDependsOnBeanFactoryPostProcessor(Class... dependencyTypes) { + super(Scheduler.class, SchedulerFactoryBean.class, dependencyTypes); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java index ce477d4354..e1b55a518c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java @@ -16,12 +16,16 @@ package org.springframework.boot.autoconfigure.quartz; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.concurrent.Executor; import javax.sql.DataSource; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.quartz.Calendar; import org.quartz.JobBuilder; import org.quartz.JobDetail; @@ -39,9 +43,11 @@ import org.quartz.simpl.RAMJobStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; @@ -51,6 +57,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; +import org.springframework.core.io.ClassPathResource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.quartz.LocalDataSourceJobStore; import org.springframework.scheduling.quartz.QuartzJobBean; @@ -73,6 +80,9 @@ public class QuartzAutoConfigurationTests { @Rule public OutputCapture output = new OutputCapture(); + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.datasource.generate-unique-name=true") .withConfiguration(AutoConfigurations.of(QuartzAutoConfiguration.class)); @@ -243,6 +253,32 @@ public class QuartzAutoConfigurationTests { }); } + @Test + public void withLiquibase() { + this.contextRunner.withUserConfiguration(QuartzJobsConfiguration.class) + .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, LiquibaseAutoConfiguration.class)) + .withPropertyValues("spring.quartz.job-store-type=jdbc", "spring.quartz.jdbc.initialize-schema=never", + "spring.liquibase.change-log=classpath:org/quartz/impl/jdbcjobstore/liquibase.quartz.init.xml") + .run(assertDataSourceJobStore("dataSource")); + } + + @Test + public void withFlyway() throws Exception { + Path flywayLocation = this.temp.newFolder().toPath(); + ClassPathResource tablesResource = new ClassPathResource("org/quartz/impl/jdbcjobstore/tables_h2.sql"); + try (InputStream stream = tablesResource.getInputStream()) { + Files.copy(stream, flywayLocation.resolve("V2__quartz.sql")); + } + this.contextRunner.withUserConfiguration(QuartzJobsConfiguration.class) + .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, FlywayAutoConfiguration.class)) + .withPropertyValues("spring.quartz.job-store-type=jdbc", "spring.quartz.jdbc.initialize-schema=never", + "spring.flyway.locations=filesystem:" + flywayLocation, + "spring.flyway.baseline-on-migrate=true") + .run(assertDataSourceJobStore("dataSource")); + } + @Test public void schedulerNameWithDedicatedProperty() { this.contextRunner.withPropertyValues("spring.quartz.scheduler-name=testScheduler")