diff --git a/spring-boot-project/spring-boot-actuator/build.gradle b/spring-boot-project/spring-boot-actuator/build.gradle index 37f73c13a09..9135e4235f3 100644 --- a/spring-boot-project/spring-boot-actuator/build.gradle +++ b/spring-boot-project/spring-boot-actuator/build.gradle @@ -85,6 +85,7 @@ dependencies { testImplementation("com.squareup.okhttp3:mockwebserver") testImplementation("org.testcontainers:junit-jupiter") + testRuntimeOnly("ch.qos.logback:logback-classic") testRuntimeOnly("io.projectreactor.netty:reactor-netty-http") testRuntimeOnly("javax.xml.bind:jaxb-api") testRuntimeOnly("org.apache.tomcat.embed:tomcat-embed-el") diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java index caf9d3c43b4..9ad82aae63e 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -115,10 +115,10 @@ class LiquibaseEndpointTests { .liquibaseBeans().getContexts().get(context.getId()).getLiquibaseBeans(); assertThat(liquibaseBeans.get("liquibase").getChangeSets()).hasSize(1); assertThat(liquibaseBeans.get("liquibase").getChangeSets().get(0).getChangeLog()) - .isEqualTo("classpath:/db/changelog/db.changelog-master.yaml"); + .isEqualTo("db/changelog/db.changelog-master.yaml"); assertThat(liquibaseBeans.get("liquibaseBackup").getChangeSets()).hasSize(1); assertThat(liquibaseBeans.get("liquibaseBackup").getChangeSets().get(0).getChangeLog()) - .isEqualTo("classpath:/db/changelog/db.changelog-master-backup.yaml"); + .isEqualTo("db/changelog/db.changelog-master-backup.yaml"); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java index 7d0c94cd51c..3015fce73ac 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -27,8 +27,6 @@ import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; import liquibase.integration.spring.SpringLiquibase; -import liquibase.logging.core.Slf4jLogger; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.condition.JRE; @@ -37,15 +35,11 @@ import org.junit.jupiter.api.io.TempDir; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.boot.DefaultBootstrapContext; -import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; -import org.springframework.boot.context.event.ApplicationStartingEvent; import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -78,12 +72,6 @@ import static org.assertj.core.api.Assertions.contentOf; @ExtendWith(OutputCaptureExtension.class) class LiquibaseAutoConfigurationTests { - @BeforeEach - void init() { - new LiquibaseServiceLocatorApplicationListener().onApplicationEvent(new ApplicationStartingEvent( - new DefaultBootstrapContext(), new SpringApplication(Object.class), new String[0])); - } - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class)) .withPropertyValues("spring.datasource.generate-unique-name=true"); @@ -315,11 +303,7 @@ class LiquibaseAutoConfigurationTests { @Test void logging(CapturedOutput output) { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .run(assertLiquibase((liquibase) -> { - Object log = ReflectionTestUtils.getField(liquibase, "log"); - assertThat(log).isInstanceOf(Slf4jLogger.class); - assertThat(output).doesNotContain(": liquibase:"); - })); + .run(assertLiquibase((liquibase) -> assertThat(output).doesNotContain(": liquibase:"))); } @Test diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 59fec09af93..92fafe3b756 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -933,12 +933,10 @@ bom { ] } } - library("Liquibase", "3.10.3") { + library("Liquibase", "4.2.2") { group("org.liquibase") { modules = [ - "liquibase-core" { - exclude group: "ch.qos.logback", module: "logback-classic" - } + "liquibase-core" ] plugins = [ "liquibase-maven-plugin" 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 index 7fe2c5d8309..92e1b13e9cb 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -16,13 +16,10 @@ 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 @@ -32,21 +29,20 @@ import org.springframework.util.StringUtils; */ class LiquibaseChangelogMissingFailureAnalyzer extends AbstractFailureAnalyzer { + private static final String MESSAGE_SUFFIX = " does not exist"; + @Override protected FailureAnalysis analyze(Throwable rootFailure, ChangeLogParseException cause) { - FileNotFoundException fileNotFound = findCause(cause, FileNotFoundException.class); - if (fileNotFound != null) { + if (cause.getMessage().endsWith(MESSAGE_SUFFIX)) { 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 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()); + return cause.getMessage().substring(0, cause.getMessage().length() - MESSAGE_SUFFIX.length()); } private String getDescription(String changelogPath) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListener.java deleted file mode 100644 index a3fc0ccafd2..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListener.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 liquibase.servicelocator.CustomResolverServiceLocator; -import liquibase.servicelocator.ServiceLocator; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.boot.context.event.ApplicationStartingEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.util.ClassUtils; - -/** - * {@link ApplicationListener} that replaces the liquibase {@link ServiceLocator} with a - * version that works with Spring Boot executable archives. - * - * @author Phillip Webb - * @author Dave Syer - * @since 1.0.0 - */ -public class LiquibaseServiceLocatorApplicationListener implements ApplicationListener { - - private static final Log logger = LogFactory.getLog(LiquibaseServiceLocatorApplicationListener.class); - - private static final boolean LIQUIBASE_PRESENT = ClassUtils.isPresent( - "liquibase.servicelocator.CustomResolverServiceLocator", - LiquibaseServiceLocatorApplicationListener.class.getClassLoader()); - - @Override - public void onApplicationEvent(ApplicationStartingEvent event) { - if (LIQUIBASE_PRESENT) { - new LiquibasePresent().replaceServiceLocator(); - } - } - - /** - * Inner class to prevent class not found issues. - */ - private static class LiquibasePresent { - - void replaceServiceLocator() { - CustomResolverServiceLocator customResolverServiceLocator = new CustomResolverServiceLocator( - new SpringPackageScanClassResolver(logger)); - ServiceLocator.setInstance(customResolverServiceLocator); - } - - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/SpringPackageScanClassResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/SpringPackageScanClassResolver.java deleted file mode 100644 index 2f188109bd1..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/SpringPackageScanClassResolver.java +++ /dev/null @@ -1,96 +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.liquibase; - -import java.io.IOException; - -import liquibase.servicelocator.DefaultPackageScanClassResolver; -import liquibase.servicelocator.PackageScanClassResolver; -import org.apache.commons.logging.Log; - -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.core.type.classreading.CachingMetadataReaderFactory; -import org.springframework.core.type.classreading.MetadataReader; -import org.springframework.core.type.classreading.MetadataReaderFactory; -import org.springframework.util.ClassUtils; - -/** - * Liquibase {@link PackageScanClassResolver} implementation that uses Spring's resource - * scanning to locate classes. This variant is safe to use with Spring Boot packaged - * executable JARs. - * - * @author Phillip Webb - * @since 1.0.0 - */ -public class SpringPackageScanClassResolver extends DefaultPackageScanClassResolver { - - private final Log logger; - - public SpringPackageScanClassResolver(Log logger) { - this.logger = logger; - } - - @Override - protected void findAllClasses(String packageName, ClassLoader loader) { - MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(loader); - try { - Resource[] resources = scan(loader, packageName); - for (Resource resource : resources) { - Class clazz = loadClass(loader, metadataReaderFactory, resource); - if (clazz != null) { - addFoundClass(clazz); - } - } - } - catch (IOException ex) { - throw new IllegalStateException(ex); - } - } - - private Resource[] scan(ClassLoader loader, String packageName) throws IOException { - ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(loader); - String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX - + ClassUtils.convertClassNameToResourcePath(packageName) + "/**/*.class"; - return resolver.getResources(pattern); - } - - private Class loadClass(ClassLoader loader, MetadataReaderFactory readerFactory, Resource resource) { - try { - MetadataReader reader = readerFactory.getMetadataReader(resource); - return ClassUtils.forName(reader.getClassMetadata().getClassName(), loader); - } - catch (ClassNotFoundException | LinkageError ex) { - handleFailure(resource, ex); - return null; - } - catch (Throwable ex) { - if (this.logger.isWarnEnabled()) { - this.logger.warn("Unexpected failure when loading class resource " + resource, ex); - } - return null; - } - } - - private void handleFailure(Resource resource, Throwable ex) { - if (this.logger.isDebugEnabled()) { - this.logger.debug("Ignoring candidate class resource " + resource + " due to " + ex); - } - } - -} 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 1c5ad2f5378..50265de54d7 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 @@ -43,8 +43,7 @@ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ -org.springframework.boot.env.EnvironmentPostProcessorApplicationListener,\ -org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener +org.springframework.boot.env.EnvironmentPostProcessorApplicationListener # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListenerTests.java deleted file mode 100644 index 127d78e7257..00000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/liquibase/LiquibaseServiceLocatorApplicationListenerTests.java +++ /dev/null @@ -1,118 +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.liquibase; - -import java.lang.reflect.Field; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.List; - -import liquibase.servicelocator.CustomResolverServiceLocator; -import liquibase.servicelocator.DefaultPackageScanClassResolver; -import liquibase.servicelocator.ServiceLocator; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.WebApplicationType; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link LiquibaseServiceLocatorApplicationListener}. - * - * @author Phillip Webb - * @author Stephane Nicoll - */ -class LiquibaseServiceLocatorApplicationListenerTests { - - private ConfigurableApplicationContext context; - - @AfterEach - void cleanUp() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - void replacesServiceLocator() throws IllegalAccessException { - SpringApplication application = new SpringApplication(Conf.class); - application.setWebApplicationType(WebApplicationType.NONE); - this.context = application.run(); - Object resolver = getClassResolver(); - assertThat(resolver).isInstanceOf(SpringPackageScanClassResolver.class); - } - - @Test - void replaceServiceLocatorBacksOffIfNotPresent() throws IllegalAccessException { - SpringApplication application = new SpringApplication(Conf.class); - application.setWebApplicationType(WebApplicationType.NONE); - DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); - resourceLoader.setClassLoader(new ClassHidingClassLoader(CustomResolverServiceLocator.class)); - application.setResourceLoader(resourceLoader); - this.context = application.run(); - Object resolver = getClassResolver(); - assertThat(resolver).isInstanceOf(DefaultPackageScanClassResolver.class); - } - - private Object getClassResolver() throws IllegalAccessException { - ServiceLocator instance = ServiceLocator.getInstance(); - Field field = ReflectionUtils.findField(ServiceLocator.class, "classResolver"); - field.setAccessible(true); - return field.get(instance); - } - - @Configuration(proxyBeanMethods = false) - static class Conf { - - } - - private final class ClassHidingClassLoader extends URLClassLoader { - - private final List> hiddenClasses; - - private ClassHidingClassLoader(Class... hiddenClasses) { - super(new URL[0], LiquibaseServiceLocatorApplicationListenerTests.class.getClassLoader()); - this.hiddenClasses = Arrays.asList(hiddenClasses); - } - - @Override - public Class loadClass(String name) throws ClassNotFoundException { - if (isHidden(name)) { - throw new ClassNotFoundException(); - } - return super.loadClass(name); - } - - private boolean isHidden(String name) { - for (Class hiddenClass : this.hiddenClasses) { - if (hiddenClass.getName().equals(name)) { - return true; - } - } - return false; - } - - } - -} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/liquibase/SpringPackageScanClassResolverTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/liquibase/SpringPackageScanClassResolverTests.java deleted file mode 100644 index 79efeababdc..00000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/liquibase/SpringPackageScanClassResolverTests.java +++ /dev/null @@ -1,42 +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.liquibase; - -import java.util.Set; - -import liquibase.logging.Logger; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for SpringPackageScanClassResolver. - * - * @author Phillip Webb - */ -class SpringPackageScanClassResolverTests { - - @Test - void testScan() { - SpringPackageScanClassResolver resolver = new SpringPackageScanClassResolver(LogFactory.getLog(getClass())); - resolver.addClassLoader(getClass().getClassLoader()); - Set> implementations = resolver.findImplementations(Logger.class, "liquibase.logging.core"); - assertThat(implementations).isNotEmpty(); - } - -}