Skip to content

Commit 3ca73bf

Browse files
ilya.lukyanovich@dataart.comwilkinsona
authored andcommitted
Auto-configure Flyway and Liquibase when there's a URL but no DataSource
See gh-16850
1 parent f59e337 commit 3ca73bf

File tree

12 files changed

+214
-15
lines changed

12 files changed

+214
-15
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@
8282
@SuppressWarnings("deprecation")
8383
@Configuration(proxyBeanMethods = false)
8484
@ConditionalOnClass(Flyway.class)
85-
@ConditionalOnBean(DataSource.class)
86-
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
85+
@ConditionalOnProperty(prefix = FlywayProperties.PROPERTIES_PREFIX, name = "enabled",
86+
matchIfMissing = true)
8787
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
8888
JdbcTemplateAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
8989
public class FlywayAutoConfiguration {
@@ -152,9 +152,12 @@ private DataSource configureDataSource(FluentConfiguration configuration,
152152
else if (flywayDataSource != null) {
153153
configuration.dataSource(flywayDataSource);
154154
}
155-
else {
155+
else if (dataSource != null) {
156156
configuration.dataSource(dataSource);
157157
}
158+
else {
159+
throw new FlywayDataSourceMissingException();
160+
}
158161
return configuration.getDataSource();
159162
}
160163

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.flyway;
18+
19+
/**
20+
* Exception thrown when no Flyway is enabled and present in classpath but no datasource
21+
* was available or configured for it.
22+
*
23+
* @author Ilya Lukyanovich
24+
*/
25+
public class FlywayDataSourceMissingException extends RuntimeException {
26+
27+
FlywayDataSourceMissingException() {
28+
super("Flyway is present in classpath and enabled but "
29+
+ "no DataSource bean neither " + FlywayProperties.PROPERTIES_PREFIX
30+
+ ".url and " + FlywayProperties.PROPERTIES_PREFIX
31+
+ ".user configuration was provided");
32+
}
33+
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.flyway;
18+
19+
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
20+
import org.springframework.boot.diagnostics.FailureAnalysis;
21+
22+
/**
23+
* A {@code FailureAnalyzer} that performs analysis of failures caused by a
24+
* {@link FlywayDataSourceMissingException}.
25+
*
26+
* @author Ilya Lukyanovich
27+
*/
28+
class FlywayDataSourceMissingFailureAnalyzer
29+
extends AbstractFailureAnalyzer<FlywayDataSourceMissingException> {
30+
31+
@Override
32+
protected FailureAnalysis analyze(Throwable rootFailure,
33+
FlywayDataSourceMissingException cause) {
34+
return new FailureAnalysis("No DataSource found for flyway",
35+
"Please provide a javax.sql.DataSource bean or flyway datasource configuration",
36+
cause);
37+
}
38+
39+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
* @author Stephane Nicoll
3636
* @since 1.1.0
3737
*/
38-
@ConfigurationProperties(prefix = "spring.flyway")
38+
@ConfigurationProperties(prefix = FlywayProperties.PROPERTIES_PREFIX)
3939
public class FlywayProperties {
4040

41+
public static final String PROPERTIES_PREFIX = "spring.flyway";
42+
4143
/**
4244
* Whether to enable flyway.
4345
*/

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@
6565
*/
6666
@Configuration(proxyBeanMethods = false)
6767
@ConditionalOnClass({ SpringLiquibase.class, DatabaseChange.class })
68-
@ConditionalOnBean(DataSource.class)
69-
@ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled",
68+
@ConditionalOnProperty(prefix = LiquibaseProperties.PROPERTIES_PREFIX, name = "enabled",
7069
matchIfMissing = true)
7170
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
7271
HibernateJpaAutoConfiguration.class })
@@ -141,17 +140,20 @@ private SpringLiquibase createSpringLiquibase(DataSource liquibaseDatasource,
141140
liquibase.setDataSource(liquibaseDataSource);
142141
return liquibase;
143142
}
144-
SpringLiquibase liquibase = new DataSourceClosingSpringLiquibase();
145-
liquibase.setDataSource(createNewDataSource(dataSourceProperties));
146-
return liquibase;
143+
else if (this.properties.isCreateDataSource()) {
144+
SpringLiquibase liquibase = new DataSourceClosingSpringLiquibase();
145+
liquibase.setDataSource(createNewDataSource(dataSourceProperties));
146+
return liquibase;
147+
}
148+
throw new LiquibaseDataSourceMissingException();
147149
}
148150

149151
private DataSource getDataSource(DataSource liquibaseDataSource,
150152
DataSource dataSource) {
151153
if (liquibaseDataSource != null) {
152154
return liquibaseDataSource;
153155
}
154-
if (this.properties.getUrl() == null && this.properties.getUser() == null) {
156+
if (!this.properties.isCreateDataSource()) {
155157
return dataSource;
156158
}
157159
return null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.liquibase;
18+
19+
/**
20+
* Exception thrown when Liquibase is enabled and present in classpath but no datasource
21+
* was available or configured for it.
22+
*
23+
* @author Ilya Lukyanovich
24+
*/
25+
public class LiquibaseDataSourceMissingException extends RuntimeException {
26+
27+
LiquibaseDataSourceMissingException() {
28+
super("Liquibase is present in classpath and enabled but "
29+
+ "no DataSource bean neither " + LiquibaseProperties.PROPERTIES_PREFIX
30+
+ ".url and " + LiquibaseProperties.PROPERTIES_PREFIX
31+
+ ".user configuration was provided");
32+
}
33+
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.liquibase;
18+
19+
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
20+
import org.springframework.boot.diagnostics.FailureAnalysis;
21+
22+
/**
23+
* A {@code FailureAnalyzer} that performs analysis of failures caused by a
24+
* {@link LiquibaseDataSourceMissingException}.
25+
*
26+
* @author Ilya Lukyanovich
27+
*/
28+
class LiquibaseDataSourceMissingFailureAnalyzer
29+
extends AbstractFailureAnalyzer<LiquibaseDataSourceMissingException> {
30+
31+
@Override
32+
protected FailureAnalysis analyze(Throwable rootFailure,
33+
LiquibaseDataSourceMissingException cause) {
34+
return new FailureAnalysis("No DataSource found for liquibsae",
35+
"Please provide a javax.sql.DataSource bean or liquibase datasource configuration",
36+
cause);
37+
}
38+
39+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseProperties.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,12 @@
3030
* @author Marcel Overdijk
3131
* @since 1.1.0
3232
*/
33-
@ConfigurationProperties(prefix = "spring.liquibase", ignoreUnknownFields = false)
33+
@ConfigurationProperties(prefix = LiquibaseProperties.PROPERTIES_PREFIX,
34+
ignoreUnknownFields = false)
3435
public class LiquibaseProperties {
3536

37+
public static final String PROPERTIES_PREFIX = "spring.liquibase";
38+
3639
/**
3740
* Change log configuration path.
3841
*/
@@ -216,6 +219,10 @@ public void setPassword(String password) {
216219
this.password = password;
217220
}
218221

222+
public boolean isCreateDataSource() {
223+
return this.url != null || this.user != null;
224+
}
225+
219226
public String getUrl() {
220227
return this.url;
221228
}

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

+2
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAuto
146146
org.springframework.boot.diagnostics.FailureAnalyzer=\
147147
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
148148
org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
149+
org.springframework.boot.autoconfigure.flyway.FlywayDataSourceMissingFailureAnalyzer,\
150+
org.springframework.boot.autoconfigure.liquibase.LiquibaseDataSourceMissingFailureAnalyzer,\
149151
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
150152
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
151153
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,25 @@ public class FlywayAutoConfigurationTests {
8080

8181
@Test
8282
public void noDataSource() {
83+
this.contextRunner.run((context) -> {
84+
assertThat(context).hasFailed();
85+
assertThat(context).getFailure().isInstanceOf(BeanCreationException.class);
86+
assertThat(context).getFailure()
87+
.hasRootCauseInstanceOf(FlywayDataSourceMissingException.class);
88+
assertThat(context).getFailure()
89+
.hasMessageContaining("Flyway is present in classpath and enabled");
90+
});
91+
}
92+
93+
@Test
94+
public void noDataSourceCreateOneWithUrl() {
8395
this.contextRunner
84-
.run((context) -> assertThat(context).doesNotHaveBean(Flyway.class));
96+
.withPropertyValues(
97+
"spring.flyway.url:jdbc:hsqldb:mem:" + UUID.randomUUID())
98+
.run((context) -> {
99+
assertThat(context).hasSingleBean(Flyway.class);
100+
assertThat(context.getBean(Flyway.class).getDataSource()).isNotNull();
101+
});
85102
}
86103

87104
@Test

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,26 @@ public void init() {
7979

8080
@Test
8181
public void noDataSource() {
82-
this.contextRunner.run(
83-
(context) -> assertThat(context).doesNotHaveBean(SpringLiquibase.class));
82+
this.contextRunner.run((context) -> {
83+
assertThat(context).hasFailed();
84+
assertThat(context).getFailure().isInstanceOf(BeanCreationException.class);
85+
assertThat(context).getFailure()
86+
.hasRootCauseInstanceOf(LiquibaseDataSourceMissingException.class);
87+
assertThat(context).getFailure().hasMessageContaining(
88+
"Liquibase is present in classpath and enabled");
89+
});
90+
}
91+
92+
@Test
93+
public void noDataSourceCreateOneWithUrl() {
94+
this.contextRunner
95+
.withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:liquibase")
96+
.run(assertLiquibase((liquibase) -> {
97+
DataSource dataSource = liquibase.getDataSource();
98+
assertThat(((HikariDataSource) dataSource).isClosed()).isTrue();
99+
assertThat(((HikariDataSource) dataSource).getJdbcUrl())
100+
.isEqualTo("jdbc:hsqldb:mem:liquibase");
101+
}));
84102
}
85103

86104
@Test

spring-boot-project/spring-boot-docs/src/main/groovy/generateConfigurationPropertyTables.groovy

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import org.springframework.boot.autoconfigure.flyway.FlywayProperties
2+
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties
13
import org.springframework.boot.configurationdocs.ConfigurationMetadataDocumentWriter
24
import org.springframework.boot.configurationdocs.DocumentOptions
35
import org.springframework.core.io.UrlResource
@@ -48,7 +50,7 @@ def generateConfigMetadataDocumentation() {
4850
.addSection("security")
4951
.withKeyPrefixes("spring.security", "spring.ldap", "spring.session")
5052
.addSection("data-migration")
51-
.withKeyPrefixes("spring.flyway", "spring.liquibase")
53+
.withKeyPrefixes(FlywayProperties.PROPERTIES_PREFIX, LiquibaseProperties.PROPERTIES_PREFIX)
5254
.addSection("data")
5355
.withKeyPrefixes("spring.couchbase", "spring.elasticsearch", "spring.h2",
5456
"spring.influx", "spring.mongodb", "spring.redis",

0 commit comments

Comments
 (0)