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

See gh-16850
pull/17002/head
ilya.lukyanovich@dataart.com 6 years ago committed by Andy Wilkinson
parent f59e337aba
commit 3ca73bf00d

@ -82,8 +82,8 @@ import org.springframework.util.StringUtils;
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Flyway.class)
@ConditionalOnBean(DataSource.class)
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
@ConditionalOnProperty(prefix = FlywayProperties.PROPERTIES_PREFIX, name = "enabled",
matchIfMissing = true)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
public class FlywayAutoConfiguration {
@ -152,9 +152,12 @@ public class FlywayAutoConfiguration {
else if (flywayDataSource != null) {
configuration.dataSource(flywayDataSource);
}
else {
else if (dataSource != null) {
configuration.dataSource(dataSource);
}
else {
throw new FlywayDataSourceMissingException();
}
return configuration.getDataSource();
}

@ -0,0 +1,34 @@
/*
* 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");
}
}

@ -0,0 +1,39 @@
/*
* 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);
}
}

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

@ -65,8 +65,7 @@ import org.springframework.util.Assert;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ SpringLiquibase.class, DatabaseChange.class })
@ConditionalOnBean(DataSource.class)
@ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled",
@ConditionalOnProperty(prefix = LiquibaseProperties.PROPERTIES_PREFIX, name = "enabled",
matchIfMissing = true)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class })
@ -141,17 +140,20 @@ 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();
}
private DataSource getDataSource(DataSource liquibaseDataSource,
DataSource dataSource) {
if (liquibaseDataSource != null) {
return liquibaseDataSource;
}
if (this.properties.getUrl() == null && this.properties.getUser() == null) {
if (!this.properties.isCreateDataSource()) {
return dataSource;
}
return null;

@ -0,0 +1,34 @@
/*
* 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");
}
}

@ -0,0 +1,39 @@
/*
* 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);
}
}

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

@ -146,6 +146,8 @@ 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

@ -80,8 +80,25 @@ public class FlywayAutoConfigurationTests {
@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");
});
}
@Test
public void noDataSourceCreateOneWithUrl() {
this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean(Flyway.class));
.withPropertyValues(
"spring.flyway.url:jdbc:hsqldb:mem:" + UUID.randomUUID())
.run((context) -> {
assertThat(context).hasSingleBean(Flyway.class);
assertThat(context.getBean(Flyway.class).getDataSource()).isNotNull();
});
}
@Test

@ -79,8 +79,26 @@ public class LiquibaseAutoConfigurationTests {
@Test
public void noDataSource() {
this.contextRunner.run(
(context) -> assertThat(context).doesNotHaveBean(SpringLiquibase.class));
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");
});
}
@Test
public void noDataSourceCreateOneWithUrl() {
this.contextRunner
.withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:liquibase")
.run(assertLiquibase((liquibase) -> {
DataSource dataSource = liquibase.getDataSource();
assertThat(((HikariDataSource) dataSource).isClosed()).isTrue();
assertThat(((HikariDataSource) dataSource).getJdbcUrl())
.isEqualTo("jdbc:hsqldb:mem:liquibase");
}));
}
@Test

@ -1,3 +1,5 @@
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
@ -48,7 +50,7 @@ def generateConfigMetadataDocumentation() {
.addSection("security")
.withKeyPrefixes("spring.security", "spring.ldap", "spring.session")
.addSection("data-migration")
.withKeyPrefixes("spring.flyway", "spring.liquibase")
.withKeyPrefixes(FlywayProperties.PROPERTIES_PREFIX, LiquibaseProperties.PROPERTIES_PREFIX)
.addSection("data")
.withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2",
"spring.influx", "spring.mongodb", "spring.redis",

Loading…
Cancel
Save