diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseChangelogMissingFailureAnalyzer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseChangelogMissingFailureAnalyzer.java new file mode 100644 index 0000000000..7fe2c5d830 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseChangelogMissingFailureAnalyzer.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2020 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.liquibase; + +import java.io.FileNotFoundException; + +import liquibase.exception.ChangeLogParseException; + +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.util.StringUtils; + +/** + * An {@link AbstractFailureAnalyzer} that analyzes exceptions of type + * {@link ChangeLogParseException} caused by a Liquibase changelog not being present. + * + * @author Sebastiaan Fernandez + */ +class LiquibaseChangelogMissingFailureAnalyzer extends AbstractFailureAnalyzer { + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, ChangeLogParseException cause) { + FileNotFoundException fileNotFound = findCause(cause, FileNotFoundException.class); + if (fileNotFound != null) { + String changelogPath = extractChangelogPath(cause); + if (StringUtils.hasText(changelogPath)) { + return new FailureAnalysis(getDescription(changelogPath), + "Make sure a Liquibase changelog is present at the configured path.", cause); + } + } + return null; + } + + private String extractChangelogPath(ChangeLogParseException cause) { + return cause.getMessage().substring("Error parsing ".length()); + } + + private String getDescription(String changelogPath) { + return "Liquibase failed to start because no changelog could be found at '" + changelogPath + "'."; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories index 8fb578e0f2..061171ccc6 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories @@ -56,7 +56,8 @@ org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer org.springframework.boot.diagnostics.analyzer.IncompatibleConfigurationFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer,\ -org.springframework.boot.diagnostics.analyzer.PatternParseFailureAnalyzer +org.springframework.boot.diagnostics.analyzer.PatternParseFailureAnalyzer,\ +org.springframework.boot.liquibase.LiquibaseChangelogMissingFailureAnalyzer # FailureAnalysisReporters org.springframework.boot.diagnostics.FailureAnalysisReporter=\ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseChangelogMissingFailureAnalyzerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseChangelogMissingFailureAnalyzerTests.java new file mode 100644 index 0000000000..39fd0ddd4a --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseChangelogMissingFailureAnalyzerTests.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012-2020 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.liquibase; + +import java.io.File; + +import javax.sql.DataSource; + +import liquibase.integration.spring.SpringLiquibase; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LiquibaseChangelogMissingFailureAnalyzer} + * + * @author Sebastiaan Fernandez + */ +class LiquibaseChangelogMissingFailureAnalyzerTests { + + @BeforeAll + static void configureDerbyLogLocation(@TempDir File temp) { + System.setProperty("derby.stream.error.file", new File(temp, "derby.log").getAbsolutePath()); + } + + @AfterAll + static void clearDerbyLogLocation(@TempDir File temp) { + System.clearProperty("derby.stream.error.file"); + } + + @Test + void changelogParseExceptionDueToChangelogNotPresent() { + FailureAnalysis analysis = performAnalysis(); + assertThat(analysis.getDescription()) + .isEqualTo("Liquibase failed to start because no changelog could be found at '" + + "classpath:/db/changelog/db.changelog-master.yaml'."); + assertThat(analysis.getAction()) + .isEqualTo("Make sure a Liquibase changelog is present at the configured path."); + } + + private FailureAnalysis performAnalysis() { + BeanCreationException failure = createFailure(); + assertThat(failure).isNotNull(); + return new LiquibaseChangelogMissingFailureAnalyzer().analyze(failure); + } + + private BeanCreationException createFailure() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + LiquibaseConfiguration.class)) { + return null; + } + catch (BeanCreationException ex) { + return ex; + } + } + + @Configuration(proxyBeanMethods = false) + static class LiquibaseConfiguration { + + @Bean + DataSource dataSource() { + return DataSourceBuilder.create().url("jdbc:hsqldb:mem:test").username("sa").build(); + } + + @Bean + SpringLiquibase springLiquibase(DataSource dataSource) { + SpringLiquibase liquibase = new SpringLiquibase(); + liquibase.setChangeLog("classpath:/db/changelog/db.changelog-master.yaml"); + liquibase.setDataSource(dataSource); + return liquibase; + } + + } + +}