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
pull/18464/head
Dmytro Nosan 5 years ago committed by Phillip Webb
parent 7150f121a3
commit 7e5bd1f281

@ -21,20 +21,27 @@ import java.util.Properties;
import javax.sql.DataSource; import javax.sql.DataSource;
import liquibase.integration.spring.SpringLiquibase;
import org.flywaydb.core.Flyway;
import org.quartz.Calendar; import org.quartz.Calendar;
import org.quartz.JobDetail; import org.quartz.JobDetail;
import org.quartz.Scheduler; import org.quartz.Scheduler;
import org.quartz.Trigger; import org.quartz.Trigger;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; 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.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
@ -56,7 +63,8 @@ import org.springframework.transaction.PlatformTransactionManager;
@Configuration @Configuration
@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class }) @ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class })
@EnableConfigurationProperties(QuartzProperties.class) @EnableConfigurationProperties(QuartzProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) @AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
LiquibaseAutoConfiguration.class, FlywayAutoConfiguration.class })
public class QuartzAutoConfiguration { public class QuartzAutoConfiguration {
private final QuartzProperties properties; private final QuartzProperties properties;
@ -157,16 +165,68 @@ public class QuartzAutoConfiguration {
return new QuartzDataSourceInitializer(dataSourceToUse, resourceLoader, properties); return new QuartzDataSourceInitializer(dataSourceToUse, resourceLoader, properties);
} }
@Bean /**
public static DataSourceInitializerSchedulerDependencyPostProcessor dataSourceInitializerSchedulerDependencyPostProcessor() { * Additional configuration to ensure that {@link SchedulerFactoryBean} and
return new DataSourceInitializerSchedulerDependencyPostProcessor(); * {@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 { 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);
} }
} }

@ -16,12 +16,16 @@
package org.springframework.boot.autoconfigure.quartz; 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 java.util.concurrent.Executor;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.quartz.Calendar; import org.quartz.Calendar;
import org.quartz.JobBuilder; import org.quartz.JobBuilder;
import org.quartz.JobDetail; import org.quartz.JobDetail;
@ -39,9 +43,11 @@ import org.quartz.simpl.RAMJobStore;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations; 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.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; 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.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer; 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.Import;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.quartz.LocalDataSourceJobStore; import org.springframework.scheduling.quartz.LocalDataSourceJobStore;
import org.springframework.scheduling.quartz.QuartzJobBean; import org.springframework.scheduling.quartz.QuartzJobBean;
@ -73,6 +80,9 @@ public class QuartzAutoConfigurationTests {
@Rule @Rule
public OutputCapture output = new OutputCapture(); public OutputCapture output = new OutputCapture();
@Rule
public TemporaryFolder temp = new TemporaryFolder();
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.generate-unique-name=true") .withPropertyValues("spring.datasource.generate-unique-name=true")
.withConfiguration(AutoConfigurations.of(QuartzAutoConfiguration.class)); .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 @Test
public void schedulerNameWithDedicatedProperty() { public void schedulerNameWithDedicatedProperty() {
this.contextRunner.withPropertyValues("spring.quartz.scheduler-name=testScheduler") this.contextRunner.withPropertyValues("spring.quartz.scheduler-name=testScheduler")

Loading…
Cancel
Save