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 1/2] 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 | 64 ++++++++++++++----- 4 files changed, 124 insertions(+), 43 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 { } + static class DataSourceConfig { + + @Bean + public DataSource dataSourceOne() { + return DataSourceBuilder.create().url("jdbc:hsqldb:mem:changelogdbtest") + .username("sa").build(); + } + + @Bean + public DataSource dataSourceTwo() { + return DataSourceBuilder.create().url("jdbc:hsqldb:mem:changelogdbtest2") + .username("sa").build(); + } + + } + @Configuration - static class MultipleFlywayBeansConfig { + static class MultipleFlywayBeansConfig extends DataSourceConfig { @Bean - Flyway flywayOne() { - return mock(Flyway.class); + public Flyway flywayOne() { + Flyway flyway = new Flyway(); + flyway.setDataSource(dataSourceOne()); + return flyway; } @Bean - Flyway flywayTwo() { - return mock(Flyway.class); + public Flyway flywayTwo() { + Flyway flyway = new Flyway(); + flyway.setDataSource(dataSourceTwo()); + return flyway; } } @Configuration - static class MultipleLiquibaseBeansConfig { + static class MultipleLiquibaseBeansConfig extends DataSourceConfig { @Bean - SpringLiquibase liquibaseOne() { - return mock(SpringLiquibase.class); + public SpringLiquibase liquibaseOne() { + SpringLiquibase liquibase = new SpringLiquibase(); + liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); + liquibase.setDataSource(dataSourceOne()); + return liquibase; } @Bean - SpringLiquibase liquibaseTwo() { - return mock(SpringLiquibase.class); + public SpringLiquibase liquibaseTwo() { + SpringLiquibase liquibase = new SpringLiquibase(); + liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); + liquibase.setDataSource(dataSourceTwo()); + return liquibase; } } From 5d909a969fcfcbd3021be5cf89fd04f44855e360 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 22 Dec 2016 11:44:35 +0100 Subject: [PATCH 2/2] Polish contribution Closes gh-6613 --- .../EndpointAutoConfiguration.java | 9 ++- .../boot/actuate/endpoint/FlywayEndpoint.java | 74 +++++++++--------- .../actuate/endpoint/LiquibaseEndpoint.java | 78 +++++++++++-------- 3 files changed, 90 insertions(+), 71 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 e41414f5bb..a3c3947c16 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 @@ -193,8 +193,8 @@ public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean - public FlywayEndpoint flywayEndpoint(List flyway) { - return new FlywayEndpoint(flyway); + public FlywayEndpoint flywayEndpoint(Map flyways) { + return new FlywayEndpoint(flyways); } } @@ -206,8 +206,9 @@ public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean - public LiquibaseEndpoint liquibaseEndpoint(List 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 f11b447a6d..8113081560 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,13 +16,9 @@ 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; @@ -31,7 +27,7 @@ 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; @@ -44,48 +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 List flyway; + private final Map flyways; public FlywayEndpoint(Flyway flyway) { - this(Collections.singletonList(flyway)); + this(Collections.singletonMap("default", flyway)); } - public FlywayEndpoint(List 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 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 - } + 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 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; } - return migrations; + + 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 9e9fcfec19..a575daaa91 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,10 +16,8 @@ package org.springframework.boot.actuate.endpoint; -import java.sql.DatabaseMetaData; -import java.sql.SQLException; +import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,9 +27,9 @@ 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.actuate.endpoint.LiquibaseEndpoint.LiquibaseReport; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.util.Assert; @@ -42,50 +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 List liquibase; + private final Map liquibases; public LiquibaseEndpoint(SpringLiquibase liquibase) { - this(Collections.singletonList(liquibase)); + this(Collections.singletonMap("default", liquibase)); } - public LiquibaseEndpoint(List 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 Map>> invoke() { - Map>> services = new HashMap>>(); - + public List invoke() { + List reports = new ArrayList(); DatabaseFactory factory = DatabaseFactory.getInstance(); - - for (SpringLiquibase liquibase : this.liquibase) { - StandardChangeLogHistoryService service = new StandardChangeLogHistoryService(); + StandardChangeLogHistoryService service = new StandardChangeLogHistoryService(); + for (Map.Entry entry : this.liquibases.entrySet()) { try { - DatabaseMetaData metaData = liquibase.getDataSource().getConnection().getMetaData(); + DataSource dataSource = entry.getValue().getDataSource(); + JdbcConnection connection = new JdbcConnection(dataSource.getConnection()); 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(); - } + Database database = factory.findCorrectDatabaseImplementation(connection); + reports.add(new LiquibaseReport(entry.getKey(), + service.queryDatabaseChangeLogTable(database))); } - 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; + + 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; + } + } }