diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/BeforeTestcontainerUsedEvent.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/BeforeTestcontainerUsedEvent.java new file mode 100644 index 00000000000..c31963302fc --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/BeforeTestcontainerUsedEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2024 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.testcontainers.lifecycle; + +import org.testcontainers.containers.Container; + +import org.springframework.context.ApplicationEvent; + +/** + * Event published just before a Testcontainers {@link Container} is used. + * + * @author Andy Wilkinson + * @since 3.2.6 + */ +public class BeforeTestcontainerUsedEvent extends ApplicationEvent { + + public BeforeTestcontainerUsedEvent(Object source) { + super(source); + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java index 7d23eb65367..7239af49515 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/lifecycle/TestcontainersLifecycleBeanPostProcessor.java @@ -39,7 +39,6 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; -import org.springframework.boot.testcontainers.properties.BeforeTestcontainersPropertySuppliedEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -61,7 +60,7 @@ import org.springframework.core.log.LogMessage; */ @Order(Ordered.LOWEST_PRECEDENCE) class TestcontainersLifecycleBeanPostProcessor - implements DestructionAwareBeanPostProcessor, ApplicationListener { + implements DestructionAwareBeanPostProcessor, ApplicationListener { private static final Log logger = LogFactory.getLog(TestcontainersLifecycleBeanPostProcessor.class); @@ -80,7 +79,7 @@ class TestcontainersLifecycleBeanPostProcessor } @Override - public void onApplicationEvent(BeforeTestcontainersPropertySuppliedEvent event) { + public void onApplicationEvent(BeforeTestcontainerUsedEvent event) { initializeContainers(); } diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/BeforeTestcontainersPropertySuppliedEvent.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/BeforeTestcontainersPropertySuppliedEvent.java index 4efe5990297..073eada8559 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/BeforeTestcontainersPropertySuppliedEvent.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/BeforeTestcontainersPropertySuppliedEvent.java @@ -18,7 +18,7 @@ package org.springframework.boot.testcontainers.properties; import java.util.function.Supplier; -import org.springframework.context.ApplicationEvent; +import org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent; /** * Event published just before the {@link Supplier value supplier} of a @@ -26,8 +26,10 @@ import org.springframework.context.ApplicationEvent; * * @author Phillip Webb * @since 3.2.2 + * @deprecated since 3.2.6 in favor of {@link BeforeTestcontainerUsedEvent} */ -public class BeforeTestcontainersPropertySuppliedEvent extends ApplicationEvent { +@Deprecated(since = "3.2.6", forRemoval = true) +public class BeforeTestcontainersPropertySuppliedEvent extends BeforeTestcontainerUsedEvent { private final String propertyName; diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java index d2df1e65b10..f1ecfe878c8 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySource.java @@ -79,6 +79,7 @@ public class TestcontainersPropertySource extends EnumerablePropertySource valueSupplier) { BeforeTestcontainersPropertySuppliedEvent event = new BeforeTestcontainersPropertySuppliedEvent(this, name); this.eventPublishers.forEach((eventPublisher) -> eventPublisher.publishEvent(event)); diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java index d2edbd9db57..b4bb38e17e7 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactory.java @@ -25,11 +25,16 @@ import org.testcontainers.containers.Container; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginProvider; +import org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.ResolvableType; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader.FailureHandler; @@ -123,10 +128,12 @@ public abstract class ContainerConnectionDetailsFactory, * @param the container type */ protected static class ContainerConnectionDetails> - implements ConnectionDetails, OriginProvider, InitializingBean { + implements ConnectionDetails, OriginProvider, InitializingBean, ApplicationContextAware { private final ContainerConnectionSource source; + private volatile ApplicationEventPublisher eventPublisher; + private volatile C container; /** @@ -151,6 +158,7 @@ public abstract class ContainerConnectionDetailsFactory, protected final C getContainer() { Assert.state(this.container != null, "Container cannot be obtained before the connection details bean has been initialized"); + this.eventPublisher.publishEvent(new BeforeTestcontainerUsedEvent(this)); return this.container; } @@ -159,6 +167,11 @@ public abstract class ContainerConnectionDetailsFactory, return this.source.getOrigin(); } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.eventPublisher = applicationContext; + } + } static class ContainerConnectionDetailsFactoriesRuntimeHints implements RuntimeHintsRegistrar { diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerContainers.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerContainers.java new file mode 100644 index 00000000000..da5f6c8a787 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerContainers.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2024 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.testcontainers; + +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; + +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * Container definitions for {@link LoadTimeWeaverAwareConsumerImportTestcontainersTests}. + * + * @author Andy Wilkinson + */ +interface LoadTimeWeaverAwareConsumerContainers { + + @Container + @ServiceConnection + PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer<>("postgres:16.1"); + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerImportTestcontainersTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerImportTestcontainersTests.java new file mode 100644 index 00000000000..75b5b7473eb --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/LoadTimeWeaverAwareConsumerImportTestcontainersTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2024 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.testcontainers; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.context.ImportTestcontainers; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.weaving.LoadTimeWeaverAware; +import org.springframework.instrument.classloading.LoadTimeWeaver; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ImportTestcontainers(LoadTimeWeaverAwareConsumerContainers.class) +public class LoadTimeWeaverAwareConsumerImportTestcontainersTests implements LoadTimeWeaverAwareConsumerContainers { + + @Autowired + private LoadTimeWeaverAwareConsumer consumer; + + @Test + void loadTimeWeaverAwareBeanCanUseJdbcUrlFromContainerBasedConnectionDetails() { + assertThat(this.consumer.jdbcUrl).isNotNull(); + } + + @Configuration + @ImportAutoConfiguration(DataSourceAutoConfiguration.class) + static class TestConfiguration { + + @Bean + LoadTimeWeaverAwareConsumer loadTimeWeaverAwareConsumer(JdbcConnectionDetails connectionDetails) { + return new LoadTimeWeaverAwareConsumer(connectionDetails); + } + + } + + static class LoadTimeWeaverAwareConsumer implements LoadTimeWeaverAware { + + private final String jdbcUrl; + + LoadTimeWeaverAwareConsumer(JdbcConnectionDetails connectionDetails) { + this.jdbcUrl = connectionDetails.getJdbcUrl(); + } + + @Override + public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) { + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java index 90103b24d78..59f1817e02a 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceAutoConfigurationTests.java @@ -49,6 +49,7 @@ class TestcontainersPropertySourceAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(TestcontainersPropertySourceAutoConfiguration.class)); @Test + @SuppressWarnings("removal") void containerBeanMethodContributesProperties() { List events = new ArrayList<>(); this.contextRunner.withUserConfiguration(ContainerAndPropertiesConfiguration.class) diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java index e11488b349c..8b913d4945b 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/properties/TestcontainersPropertySourceTests.java @@ -134,6 +134,7 @@ class TestcontainersPropertySourceTests { } @Test + @SuppressWarnings("removal") void getPropertyPublishesEvent() { try (GenericApplicationContext applicationContext = new GenericApplicationContext()) { List events = new ArrayList<>(); diff --git a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactoryTests.java b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactoryTests.java index bab9bf3488f..d1039f3e75b 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactoryTests.java +++ b/spring-boot-project/spring-boot-testcontainers/src/test/java/org/springframework/boot/testcontainers/service/connection/ContainerConnectionDetailsFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -28,11 +28,15 @@ import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; import org.springframework.boot.origin.Origin; +import org.springframework.boot.testcontainers.lifecycle.BeforeTestcontainerUsedEvent; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactoryTests.TestContainerConnectionDetailsFactory.TestContainerConnectionDetails; +import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.MergedAnnotation; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; /** @@ -112,11 +116,14 @@ class ContainerConnectionDetailsFactoryTests { } @Test - void getContainerWhenInitializedReturnsSuppliedContainer() throws Exception { + void getContainerWhenInitializedPublishesEventAndReturnsSuppliedContainer() throws Exception { TestContainerConnectionDetailsFactory factory = new TestContainerConnectionDetailsFactory(); TestContainerConnectionDetails connectionDetails = getConnectionDetails(factory, this.source); + ApplicationContext context = mock(ApplicationContext.class); + connectionDetails.setApplicationContext(context); connectionDetails.afterPropertiesSet(); assertThat(connectionDetails.callGetContainer()).isSameAs(this.container); + then(context).should().publishEvent(any(BeforeTestcontainerUsedEvent.class)); } @SuppressWarnings({ "rawtypes", "unchecked" }) diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index fdec73a1639..238b5d5c6c5 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -82,4 +82,5 @@ +