Polish "Auto-configure Flyway and Liquibase when there's a URL but no DataSource"

See gh-16850
This commit is contained in:
Andy Wilkinson 2019-05-29 10:46:55 +01:00
parent 3ca73bf00d
commit 880721557b
12 changed files with 64 additions and 196 deletions

View File

@ -36,11 +36,13 @@ import org.flywaydb.core.api.configuration.FluentConfiguration;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
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.data.jpa.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDataSourceCondition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.JdbcOperationsDependsOnPostProcessor;
@ -52,6 +54,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
@ -82,8 +85,8 @@ import org.springframework.util.StringUtils;
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Flyway.class)
@ConditionalOnProperty(prefix = FlywayProperties.PROPERTIES_PREFIX, name = "enabled",
matchIfMissing = true)
@Conditional(FlywayDataSourceCondition.class)
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
public class FlywayAutoConfiguration {
@ -152,11 +155,8 @@ public class FlywayAutoConfiguration {
else if (flywayDataSource != null) {
configuration.dataSource(flywayDataSource);
}
else if (dataSource != null) {
configuration.dataSource(dataSource);
}
else {
throw new FlywayDataSourceMissingException();
configuration.dataSource(dataSource);
}
return configuration.getDataSource();
}
@ -465,4 +465,23 @@ public class FlywayAutoConfiguration {
}
static final class FlywayDataSourceCondition extends AnyNestedCondition {
FlywayDataSourceCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(DataSource.class)
private static final class DataSourceBeanCondition {
}
@ConditionalOnProperty(prefix = "spring.flyway", name = "url",
matchIfMissing = false)
private static final class FlywayUrlCondition {
}
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.flyway;
/**
* Exception thrown when no Flyway is enabled and present in classpath but no datasource
* was available or configured for it.
*
* @author Ilya Lukyanovich
*/
public class FlywayDataSourceMissingException extends RuntimeException {
FlywayDataSourceMissingException() {
super("Flyway is present in classpath and enabled but "
+ "no DataSource bean neither " + FlywayProperties.PROPERTIES_PREFIX
+ ".url and " + FlywayProperties.PROPERTIES_PREFIX
+ ".user configuration was provided");
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.flyway;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
/**
* A {@code FailureAnalyzer} that performs analysis of failures caused by a
* {@link FlywayDataSourceMissingException}.
*
* @author Ilya Lukyanovich
*/
class FlywayDataSourceMissingFailureAnalyzer
extends AbstractFailureAnalyzer<FlywayDataSourceMissingException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure,
FlywayDataSourceMissingException cause) {
return new FailureAnalysis("No DataSource found for flyway",
"Please provide a javax.sql.DataSource bean or flyway datasource configuration",
cause);
}
}

View File

@ -35,11 +35,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Stephane Nicoll
* @since 1.1.0
*/
@ConfigurationProperties(prefix = FlywayProperties.PROPERTIES_PREFIX)
@ConfigurationProperties(prefix = "spring.flyway")
public class FlywayProperties {
public static final String PROPERTIES_PREFIX = "spring.flyway";
/**
* Whether to enable flyway.
*/

View File

@ -28,6 +28,7 @@ import liquibase.integration.spring.SpringLiquibase;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -37,10 +38,12 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.JdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.jdbc.NamedParameterJdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseDataSourceCondition;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.Resource;
@ -65,8 +68,9 @@ import org.springframework.util.Assert;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ SpringLiquibase.class, DatabaseChange.class })
@ConditionalOnProperty(prefix = LiquibaseProperties.PROPERTIES_PREFIX, name = "enabled",
@ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled",
matchIfMissing = true)
@Conditional(LiquibaseDataSourceCondition.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class })
public class LiquibaseAutoConfiguration {
@ -140,12 +144,9 @@ public class LiquibaseAutoConfiguration {
liquibase.setDataSource(liquibaseDataSource);
return liquibase;
}
else if (this.properties.isCreateDataSource()) {
SpringLiquibase liquibase = new DataSourceClosingSpringLiquibase();
liquibase.setDataSource(createNewDataSource(dataSourceProperties));
return liquibase;
}
throw new LiquibaseDataSourceMissingException();
SpringLiquibase liquibase = new DataSourceClosingSpringLiquibase();
liquibase.setDataSource(createNewDataSource(dataSourceProperties));
return liquibase;
}
private DataSource getDataSource(DataSource liquibaseDataSource,
@ -153,7 +154,7 @@ public class LiquibaseAutoConfiguration {
if (liquibaseDataSource != null) {
return liquibaseDataSource;
}
if (!this.properties.isCreateDataSource()) {
if (this.properties.getUrl() == null && this.properties.getUser() == null) {
return dataSource;
}
return null;
@ -227,4 +228,23 @@ public class LiquibaseAutoConfiguration {
}
static final class LiquibaseDataSourceCondition extends AnyNestedCondition {
LiquibaseDataSourceCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(DataSource.class)
private static final class DataSourceBeanCondition {
}
@ConditionalOnProperty(prefix = "spring.liquibase", name = "url",
matchIfMissing = false)
private static final class LiquibaseUrlCondition {
}
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.liquibase;
/**
* Exception thrown when Liquibase is enabled and present in classpath but no datasource
* was available or configured for it.
*
* @author Ilya Lukyanovich
*/
public class LiquibaseDataSourceMissingException extends RuntimeException {
LiquibaseDataSourceMissingException() {
super("Liquibase is present in classpath and enabled but "
+ "no DataSource bean neither " + LiquibaseProperties.PROPERTIES_PREFIX
+ ".url and " + LiquibaseProperties.PROPERTIES_PREFIX
+ ".user configuration was provided");
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.liquibase;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
/**
* A {@code FailureAnalyzer} that performs analysis of failures caused by a
* {@link LiquibaseDataSourceMissingException}.
*
* @author Ilya Lukyanovich
*/
class LiquibaseDataSourceMissingFailureAnalyzer
extends AbstractFailureAnalyzer<LiquibaseDataSourceMissingException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure,
LiquibaseDataSourceMissingException cause) {
return new FailureAnalysis("No DataSource found for liquibsae",
"Please provide a javax.sql.DataSource bean or liquibase datasource configuration",
cause);
}
}

View File

@ -30,12 +30,9 @@ import org.springframework.util.Assert;
* @author Marcel Overdijk
* @since 1.1.0
*/
@ConfigurationProperties(prefix = LiquibaseProperties.PROPERTIES_PREFIX,
ignoreUnknownFields = false)
@ConfigurationProperties(prefix = "spring.liquibase", ignoreUnknownFields = false)
public class LiquibaseProperties {
public static final String PROPERTIES_PREFIX = "spring.liquibase";
/**
* Change log configuration path.
*/
@ -219,10 +216,6 @@ public class LiquibaseProperties {
this.password = password;
}
public boolean isCreateDataSource() {
return this.url != null || this.user != null;
}
public String getUrl() {
return this.url;
}

View File

@ -146,8 +146,6 @@ org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAuto
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
org.springframework.boot.autoconfigure.flyway.FlywayDataSourceMissingFailureAnalyzer,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseDataSourceMissingFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

View File

@ -79,19 +79,13 @@ public class FlywayAutoConfigurationTests {
.withPropertyValues("spring.datasource.generate-unique-name=true");
@Test
public void noDataSource() {
this.contextRunner.run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure().isInstanceOf(BeanCreationException.class);
assertThat(context).getFailure()
.hasRootCauseInstanceOf(FlywayDataSourceMissingException.class);
assertThat(context).getFailure()
.hasMessageContaining("Flyway is present in classpath and enabled");
});
public void backsOffWithNoDataSourceBeanAndNoFlywayUrl() {
this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean(Flyway.class));
}
@Test
public void noDataSourceCreateOneWithUrl() {
public void createsDataSourceWithNoDataSourceBeanAndFlywayUrl() {
this.contextRunner
.withPropertyValues(
"spring.flyway.url:jdbc:hsqldb:mem:" + UUID.randomUUID())

View File

@ -78,19 +78,13 @@ public class LiquibaseAutoConfigurationTests {
.withPropertyValues("spring.datasource.generate-unique-name=true");
@Test
public void noDataSource() {
this.contextRunner.run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure().isInstanceOf(BeanCreationException.class);
assertThat(context).getFailure()
.hasRootCauseInstanceOf(LiquibaseDataSourceMissingException.class);
assertThat(context).getFailure().hasMessageContaining(
"Liquibase is present in classpath and enabled");
});
public void backsOffWithNoDataSourceBeanAndNoLiquibaseUrl() {
this.contextRunner.run(
(context) -> assertThat(context).doesNotHaveBean(SpringLiquibase.class));
}
@Test
public void noDataSourceCreateOneWithUrl() {
public void createsDataSourceWithNoDataSourceBeanAndLiquibaseUrl() {
this.contextRunner
.withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:liquibase")
.run(assertLiquibase((liquibase) -> {

View File

@ -1,5 +1,3 @@
import org.springframework.boot.autoconfigure.flyway.FlywayProperties
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties
import org.springframework.boot.configurationdocs.ConfigurationMetadataDocumentWriter
import org.springframework.boot.configurationdocs.DocumentOptions
import org.springframework.core.io.UrlResource
@ -50,7 +48,7 @@ def generateConfigMetadataDocumentation() {
.addSection("security")
.withKeyPrefixes("spring.security", "spring.ldap", "spring.session")
.addSection("data-migration")
.withKeyPrefixes(FlywayProperties.PROPERTIES_PREFIX, LiquibaseProperties.PROPERTIES_PREFIX)
.withKeyPrefixes("spring.flyway", "spring.liquibase")
.addSection("data")
.withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2",
"spring.influx", "spring.mongodb", "spring.redis",