diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java index 909fd041fa6..a3c3947c164 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java @@ -55,7 +55,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionEvaluationRepor 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.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; @@ -188,27 +187,28 @@ public class EndpointAutoConfiguration { } @Configuration - @ConditionalOnSingleCandidate(Flyway.class) + @ConditionalOnBean(Flyway.class) @ConditionalOnClass(Flyway.class) static class FlywayEndpointConfiguration { @Bean @ConditionalOnMissingBean - public FlywayEndpoint flywayEndpoint(Flyway flyway) { - return new FlywayEndpoint(flyway); + public FlywayEndpoint flywayEndpoint(Map flyways) { + return new FlywayEndpoint(flyways); } } @Configuration - @ConditionalOnSingleCandidate(SpringLiquibase.class) + @ConditionalOnBean(SpringLiquibase.class) @ConditionalOnClass(SpringLiquibase.class) static class LiquibaseEndpointConfiguration { @Bean @ConditionalOnMissingBean - public LiquibaseEndpoint liquibaseEndpoint(SpringLiquibase liquibase) { - return new LiquibaseEndpoint(liquibase); + public LiquibaseEndpoint liquibaseEndpoint( + Map liquibases) { + return new LiquibaseEndpoint(liquibases); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java index 35dbcf2dbd5..8113081560e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/FlywayEndpoint.java @@ -17,15 +17,17 @@ package org.springframework.boot.actuate.endpoint; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Map; import org.flywaydb.core.Flyway; import org.flywaydb.core.api.MigrationInfo; import org.flywaydb.core.api.MigrationState; import org.flywaydb.core.api.MigrationType; -import org.springframework.boot.actuate.endpoint.FlywayEndpoint.FlywayMigration; +import org.springframework.boot.actuate.endpoint.FlywayEndpoint.FlywayReport; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.util.Assert; @@ -38,23 +40,54 @@ import org.springframework.util.Assert; * @since 1.3.0 */ @ConfigurationProperties(prefix = "endpoints.flyway") -public class FlywayEndpoint extends AbstractEndpoint> { +public class FlywayEndpoint extends AbstractEndpoint> { - private final Flyway flyway; + private final Map flyways; public FlywayEndpoint(Flyway flyway) { + this(Collections.singletonMap("default", flyway)); + } + + public FlywayEndpoint(Map flyways) { super("flyway"); - Assert.notNull(flyway, "Flyway must not be null"); - this.flyway = flyway; + Assert.notEmpty(flyways, "Flyways must be specified"); + this.flyways = flyways; } @Override - public List invoke() { - List migrations = new ArrayList(); - for (MigrationInfo info : this.flyway.info().all()) { - migrations.add(new FlywayMigration(info)); + public List invoke() { + List reports = new ArrayList(); + for (Map.Entry entry : this.flyways.entrySet()) { + List migrations = new ArrayList(); + for (MigrationInfo info : entry.getValue().info().all()) { + migrations.add(new FlywayMigration(info)); + } + reports.add(new FlywayReport(entry.getKey(), migrations)); } - return migrations; + return reports; + } + + /** + * Flyway report for one datasource. + */ + public static class FlywayReport { + + private final String name; + private final List migrations; + + public FlywayReport(String name, List migrations) { + this.name = name; + this.migrations = migrations; + } + + public String getName() { + return this.name; + } + + public List getMigrations() { + return this.migrations; + } + } /** diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java index 132567fb6dd..a575daaa91f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/LiquibaseEndpoint.java @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.endpoint; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -27,6 +29,7 @@ import liquibase.database.DatabaseFactory; import liquibase.database.jvm.JdbcConnection; import liquibase.integration.spring.SpringLiquibase; +import org.springframework.boot.actuate.endpoint.LiquibaseEndpoint.LiquibaseReport; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.util.Assert; @@ -37,34 +40,68 @@ import org.springframework.util.Assert; * @since 1.3.0 */ @ConfigurationProperties(prefix = "endpoints.liquibase") -public class LiquibaseEndpoint extends AbstractEndpoint>> { +public class LiquibaseEndpoint extends AbstractEndpoint> { - private final SpringLiquibase liquibase; + private final Map liquibases; public LiquibaseEndpoint(SpringLiquibase liquibase) { + this(Collections.singletonMap("default", liquibase)); + } + + public LiquibaseEndpoint(Map liquibase) { super("liquibase"); - Assert.notNull(liquibase, "Liquibase must not be null"); - this.liquibase = liquibase; + Assert.notEmpty(liquibase, "Liquibase must be specified"); + this.liquibases = liquibase; } @Override - public List> invoke() { + public List invoke() { + List reports = new ArrayList(); + DatabaseFactory factory = DatabaseFactory.getInstance(); StandardChangeLogHistoryService service = new StandardChangeLogHistoryService(); - try { - DatabaseFactory factory = DatabaseFactory.getInstance(); - DataSource dataSource = this.liquibase.getDataSource(); - JdbcConnection connection = new JdbcConnection(dataSource.getConnection()); + for (Map.Entry entry : this.liquibases.entrySet()) { try { - Database database = factory.findCorrectDatabaseImplementation(connection); - return service.queryDatabaseChangeLogTable(database); + DataSource dataSource = entry.getValue().getDataSource(); + JdbcConnection connection = new JdbcConnection(dataSource.getConnection()); + try { + Database database = factory.findCorrectDatabaseImplementation(connection); + reports.add(new LiquibaseReport(entry.getKey(), + service.queryDatabaseChangeLogTable(database))); + } + finally { + connection.close(); + } } - finally { - connection.close(); + catch (Exception ex) { + throw new IllegalStateException("Unable to get Liquibase changelog", ex); } } - catch (Exception ex) { - throw new IllegalStateException("Unable to get Liquibase changelog", ex); + + return reports; + } + + /** + * Liquibase report for one datasource. + */ + public static class LiquibaseReport { + + private final String name; + + private final List> changeLogs; + + public LiquibaseReport(String name, List> changeLogs) { + this.name = name; + this.changeLogs = changeLogs; } + + public String getName() { + return this.name; + } + + public List> getChangeLogs() { + return this.changeLogs; + } + } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java index 4262560a70f..1010d900fd9 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java @@ -23,6 +23,8 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; +import javax.sql.DataSource; + import liquibase.integration.spring.SpringLiquibase; import org.flywaydb.core.Flyway; import org.junit.After; @@ -50,6 +52,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionEvaluationRepor import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration; import org.springframework.boot.autoconfigure.info.ProjectInfoProperties; +import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.bind.PropertySourcesBinder; @@ -65,7 +68,6 @@ import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.validation.BindException; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; /** * Tests for {@link EndpointAutoConfiguration}. @@ -222,12 +224,13 @@ public class EndpointAutoConfigurationTests { } @Test - public void flywayEndpointIsDisabledWhenThereAreMultipleFlywayBeans() { + public void testFlywayEndpointWithMultipleFlywayBeans() { this.context = new AnnotationConfigApplicationContext(); this.context.register(MultipleFlywayBeansConfig.class, - EndpointAutoConfiguration.class); + FlywayAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); - assertThat(this.context.getBeansOfType(FlywayEndpoint.class)).hasSize(0); + assertThat(this.context.getBeansOfType(Flyway.class)).hasSize(2); + assertThat(this.context.getBeansOfType(FlywayEndpoint.class)).hasSize(1); } @Test @@ -242,12 +245,13 @@ public class EndpointAutoConfigurationTests { } @Test - public void liquibaseEndpointIsDisabledWhenThereAreMultipleSpringLiquibaseBeans() { + public void testLiquibaseEndpointWithMultipleSpringLiquibaseBeans() { this.context = new AnnotationConfigApplicationContext(); this.context.register(MultipleLiquibaseBeansConfig.class, - EndpointAutoConfiguration.class); + LiquibaseAutoConfiguration.class, EndpointAutoConfiguration.class); this.context.refresh(); - assertThat(this.context.getBeansOfType(LiquibaseEndpoint.class)).hasSize(0); + assertThat(this.context.getBeansOfType(SpringLiquibase.class)).hasSize(2); + assertThat(this.context.getBeansOfType(LiquibaseEndpoint.class)).hasSize(1); } private void load(Class... config) { @@ -330,32 +334,58 @@ public class EndpointAutoConfigurationTests { } - @Configuration - static class MultipleFlywayBeansConfig { + static class DataSourceConfig { @Bean - Flyway flywayOne() { - return mock(Flyway.class); + public DataSource dataSourceOne() { + return DataSourceBuilder.create().url("jdbc:hsqldb:mem:changelogdbtest") + .username("sa").build(); } @Bean - Flyway flywayTwo() { - return mock(Flyway.class); + public DataSource dataSourceTwo() { + return DataSourceBuilder.create().url("jdbc:hsqldb:mem:changelogdbtest2") + .username("sa").build(); } } @Configuration - static class MultipleLiquibaseBeansConfig { + static class MultipleFlywayBeansConfig extends DataSourceConfig { @Bean - SpringLiquibase liquibaseOne() { - return mock(SpringLiquibase.class); + public Flyway flywayOne() { + Flyway flyway = new Flyway(); + flyway.setDataSource(dataSourceOne()); + return flyway; } @Bean - SpringLiquibase liquibaseTwo() { - return mock(SpringLiquibase.class); + public Flyway flywayTwo() { + Flyway flyway = new Flyway(); + flyway.setDataSource(dataSourceTwo()); + return flyway; + } + + } + + @Configuration + static class MultipleLiquibaseBeansConfig extends DataSourceConfig { + + @Bean + public SpringLiquibase liquibaseOne() { + SpringLiquibase liquibase = new SpringLiquibase(); + liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); + liquibase.setDataSource(dataSourceOne()); + return liquibase; + } + + @Bean + public SpringLiquibase liquibaseTwo() { + SpringLiquibase liquibase = new SpringLiquibase(); + liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); + liquibase.setDataSource(dataSourceTwo()); + return liquibase; } }