From 53d25999f388ee0322a7b2b92e737bb5dd2e7d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Thu, 11 Aug 2016 14:26:46 +1000 Subject: [PATCH] Add support for multiple beans in the Flyway and Liquibase endpoints Closes gh-6610 See gh-6613 --- .../EndpointAutoConfiguration.java | 9 ++- .../boot/actuate/endpoint/FlywayEndpoint.java | 43 ++++++++++-- .../actuate/endpoint/LiquibaseEndpoint.java | 51 +++++++++----- .../EndpointAutoConfigurationTests.java | 66 ++++++++++++++----- 4 files changed, 125 insertions(+), 44 deletions(-) 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 909fd041fa..e41414f5bb 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,26 +187,26 @@ public class EndpointAutoConfiguration { } @Configuration - @ConditionalOnSingleCandidate(Flyway.class) + @ConditionalOnBean(Flyway.class) @ConditionalOnClass(Flyway.class) static class FlywayEndpointConfiguration { @Bean @ConditionalOnMissingBean - public FlywayEndpoint flywayEndpoint(Flyway flyway) { + public FlywayEndpoint flywayEndpoint(List flyway) { return new FlywayEndpoint(flyway); } } @Configuration - @ConditionalOnSingleCandidate(SpringLiquibase.class) + @ConditionalOnBean(SpringLiquibase.class) @ConditionalOnClass(SpringLiquibase.class) static class LiquibaseEndpointConfiguration { @Bean @ConditionalOnMissingBean - public LiquibaseEndpoint liquibaseEndpoint(SpringLiquibase liquibase) { + public LiquibaseEndpoint liquibaseEndpoint(List liquibase) { return new LiquibaseEndpoint(liquibase); } 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 35dbcf2dbd..f11b447a6d 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 @@ -16,9 +16,15 @@ package org.springframework.boot.actuate.endpoint; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.flywaydb.core.Flyway; import org.flywaydb.core.api.MigrationInfo; @@ -38,21 +44,46 @@ 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 List flyway; public FlywayEndpoint(Flyway flyway) { + this(Collections.singletonList(flyway)); + } + + public FlywayEndpoint(List flyway) { super("flyway"); Assert.notNull(flyway, "Flyway must not be null"); this.flyway = flyway; } @Override - public List invoke() { - List migrations = new ArrayList(); - for (MigrationInfo info : this.flyway.info().all()) { - migrations.add(new FlywayMigration(info)); + public Map> invoke() { + Map> migrations = new HashMap>(); + for (Flyway flyway : this.flyway) { + Connection connection = null; + try { + connection = flyway.getDataSource().getConnection(); + DatabaseMetaData metaData = connection.getMetaData(); + + List migration = new ArrayList(); + for (MigrationInfo info : flyway.info().all()) { + migration.add(new FlywayMigration(info)); + } + migrations.put(metaData.getURL(), migration); + } + catch (SQLException e) { + //Continue + } + finally { + try { + connection.close(); + } + catch (SQLException e) { + //Continue + } + } } return 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 132567fb6d..9e9fcfec19 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,10 @@ package org.springframework.boot.actuate.endpoint; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,6 +29,7 @@ import liquibase.changelog.StandardChangeLogHistoryService; import liquibase.database.Database; import liquibase.database.DatabaseFactory; import liquibase.database.jvm.JdbcConnection; +import liquibase.exception.DatabaseException; import liquibase.integration.spring.SpringLiquibase; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -37,34 +42,50 @@ 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 List liquibase; public LiquibaseEndpoint(SpringLiquibase liquibase) { + this(Collections.singletonList(liquibase)); + } + + public LiquibaseEndpoint(List liquibase) { super("liquibase"); Assert.notNull(liquibase, "Liquibase must not be null"); this.liquibase = liquibase; } @Override - public List> invoke() { - StandardChangeLogHistoryService service = new StandardChangeLogHistoryService(); - try { - DatabaseFactory factory = DatabaseFactory.getInstance(); - DataSource dataSource = this.liquibase.getDataSource(); - JdbcConnection connection = new JdbcConnection(dataSource.getConnection()); + public Map>> invoke() { + Map>> services = new HashMap>>(); + + DatabaseFactory factory = DatabaseFactory.getInstance(); + + for (SpringLiquibase liquibase : this.liquibase) { + StandardChangeLogHistoryService service = new StandardChangeLogHistoryService(); try { - Database database = factory.findCorrectDatabaseImplementation(connection); - return service.queryDatabaseChangeLogTable(database); + DatabaseMetaData metaData = liquibase.getDataSource().getConnection().getMetaData(); + try { + DataSource dataSource = liquibase.getDataSource(); + JdbcConnection connection = new JdbcConnection(dataSource.getConnection()); + try { + Database database = factory.findCorrectDatabaseImplementation(connection); + services.put(metaData.getURL(), service.queryDatabaseChangeLogTable(database)); + } + finally { + connection.close(); + } + } + catch (DatabaseException ex) { + throw new IllegalStateException("Unable to get Liquibase changelog", ex); + } } - finally { - connection.close(); + catch (SQLException e) { + //Continue } } - catch (Exception ex) { - throw new IllegalStateException("Unable to get Liquibase changelog", ex); - } + return services; } } 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 4262560a70..1010d900fd 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; } }