From 97a29861cd511c8882c0abd65c7984d9370ebdb3 Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Sun, 22 Sep 2024 19:10:30 +0300 Subject: [PATCH] Provide service connection support for Hazelcast --- .../HazelcastClientConfiguration.java | 43 +++------- .../HazelcastClientInstanceConfiguration.java | 46 ++++++++++ .../hazelcast/HazelcastConnectionDetails.java | 37 ++++++++ .../PropertiesHazelcastConnectionDetails.java | 68 +++++++++++++++ ...HazelcastAutoConfigurationClientTests.java | 30 +++++++ .../spring-boot-docker-compose/build.gradle | 3 + ...nectionDetailsFactoryIntegrationTests.java | 66 ++++++++++++++ .../hazelcast-cluster-name-compose.yaml | 7 ++ .../hazelcast/hazelcast-compose.yaml | 5 ++ ...DockerComposeConnectionDetailsFactory.java | 76 ++++++++++++++++ .../hazelcast/HazelcastEnvironment.java | 39 +++++++++ .../connection/hazelcast/package-info.java | 20 +++++ .../main/resources/META-INF/spring.factories | 3 +- .../hazelcast/HazelcastEnvironmentTests.java | 45 ++++++++++ .../pages/features/dev-services.adoc | 3 + .../spring-boot-testcontainers/build.gradle | 2 + ...nectionDetailsFactoryIntegrationTests.java | 86 +++++++++++++++++++ ...nectionDetailsFactoryIntegrationTests.java | 72 ++++++++++++++++ ...castContainerConnectionDetailsFactory.java | 76 ++++++++++++++++ .../connection/hazelcast/package-info.java | 20 +++++ .../main/resources/META-INF/spring.factories | 3 +- .../container/HazelcastContainer.java | 36 ++++++++ .../boot/testsupport/container/TestImage.java | 5 ++ 23 files changed, 756 insertions(+), 35 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientInstanceConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetails.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/PropertiesHazelcastConnectionDetails.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-cluster-name-compose.yaml create mode 100644 spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-compose.yaml create mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactory.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironment.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/package-info.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironmentTests.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactory.java create mode 100644 spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/package-info.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/HazelcastContainer.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java index 570c7a51a25e..7bfcf7cf2c93 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 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. @@ -16,13 +16,8 @@ package org.springframework.boot.autoconfigure.hazelcast; -import java.io.IOException; -import java.net.URL; - import com.hazelcast.client.HazelcastClient; import com.hazelcast.client.config.ClientConfig; -import com.hazelcast.client.config.XmlClientConfigBuilder; -import com.hazelcast.client.config.YamlClientConfigBuilder; import com.hazelcast.core.HazelcastInstance; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -31,9 +26,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; +import org.springframework.context.annotation.Import; import org.springframework.core.io.ResourceLoader; -import org.springframework.util.StringUtils; /** * Configuration for Hazelcast client. @@ -44,49 +38,32 @@ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(HazelcastClient.class) @ConditionalOnMissingBean(HazelcastInstance.class) +@Import(HazelcastClientInstanceConfiguration.class) class HazelcastClientConfiguration { static final String CONFIG_SYSTEM_PROPERTY = "hazelcast.client.config"; - private static HazelcastInstance getHazelcastInstance(ClientConfig config) { - if (StringUtils.hasText(config.getInstanceName())) { - return HazelcastClient.getOrCreateHazelcastClient(config); - } - return HazelcastClient.newHazelcastClient(config); - } - @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingBean(ClientConfig.class) + @ConditionalOnMissingBean({ ClientConfig.class, HazelcastConnectionDetails.class }) @Conditional(HazelcastClientConfigAvailableCondition.class) static class HazelcastClientConfigFileConfiguration { @Bean - HazelcastInstance hazelcastInstance(HazelcastProperties properties, ResourceLoader resourceLoader) - throws IOException { - Resource configLocation = properties.resolveConfigLocation(); - ClientConfig config = (configLocation != null) ? loadClientConfig(configLocation) : ClientConfig.load(); - config.setClassLoader(resourceLoader.getClassLoader()); - return getHazelcastInstance(config); - } - - private ClientConfig loadClientConfig(Resource configLocation) throws IOException { - URL configUrl = configLocation.getURL(); - String configFileName = configUrl.getPath(); - if (configFileName.endsWith(".yaml") || configFileName.endsWith(".yml")) { - return new YamlClientConfigBuilder(configUrl).build(); - } - return new XmlClientConfigBuilder(configUrl).build(); + HazelcastConnectionDetails hazelcastConnectionDetails(HazelcastProperties properties, + ResourceLoader resourceLoader) { + return new PropertiesHazelcastConnectionDetails(properties, resourceLoader); } } @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(HazelcastConnectionDetails.class) @ConditionalOnSingleCandidate(ClientConfig.class) static class HazelcastClientConfigConfiguration { @Bean - HazelcastInstance hazelcastInstance(ClientConfig config) { - return getHazelcastInstance(config); + HazelcastConnectionDetails hazelcastConnectionDetails(ClientConfig config) { + return () -> config; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientInstanceConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientInstanceConfiguration.java new file mode 100644 index 000000000000..f3f7acd0c954 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientInstanceConfiguration.java @@ -0,0 +1,46 @@ +/* + * 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.autoconfigure.hazelcast; + +import com.hazelcast.client.HazelcastClient; +import com.hazelcast.client.config.ClientConfig; +import com.hazelcast.core.HazelcastInstance; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +/** + * Configuration for Hazelcast client instance. + * + * @author Dmytro Nosan + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnBean(HazelcastConnectionDetails.class) +class HazelcastClientInstanceConfiguration { + + @Bean + HazelcastInstance hazelcastInstance(HazelcastConnectionDetails hazelcastConnectionDetails) { + ClientConfig config = hazelcastConnectionDetails.getClientConfig(); + if (StringUtils.hasText(config.getInstanceName())) { + return HazelcastClient.getOrCreateHazelcastClient(config); + } + return HazelcastClient.newHazelcastClient(config); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetails.java new file mode 100644 index 000000000000..7b98f817d6c6 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetails.java @@ -0,0 +1,37 @@ +/* + * 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.autoconfigure.hazelcast; + +import com.hazelcast.client.config.ClientConfig; + +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Details required to establish a client connection to a Hazelcast instance. + * + * @author Dmytro Nosan + * @since 3.4.0 + */ +public interface HazelcastConnectionDetails extends ConnectionDetails { + + /** + * The {@link ClientConfig} for Hazelcast client. + * @return the client config + */ + ClientConfig getClientConfig(); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/PropertiesHazelcastConnectionDetails.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/PropertiesHazelcastConnectionDetails.java new file mode 100644 index 000000000000..34d23390e67e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/PropertiesHazelcastConnectionDetails.java @@ -0,0 +1,68 @@ +/* + * 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.autoconfigure.hazelcast; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URL; + +import com.hazelcast.client.config.ClientConfig; +import com.hazelcast.client.config.XmlClientConfigBuilder; +import com.hazelcast.client.config.YamlClientConfigBuilder; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +/** + * Adapts {@link HazelcastProperties} to {@link HazelcastConnectionDetails}. + * + * @author Dmytro Nosan + */ +class PropertiesHazelcastConnectionDetails implements HazelcastConnectionDetails { + + private final HazelcastProperties properties; + + private final ResourceLoader resourceLoader; + + PropertiesHazelcastConnectionDetails(HazelcastProperties properties, ResourceLoader resourceLoader) { + this.properties = properties; + this.resourceLoader = resourceLoader; + } + + @Override + public ClientConfig getClientConfig() { + Resource configLocation = this.properties.resolveConfigLocation(); + ClientConfig config = (configLocation != null) ? loadClientConfig(configLocation) : ClientConfig.load(); + config.setClassLoader(this.resourceLoader.getClassLoader()); + return config; + } + + private ClientConfig loadClientConfig(Resource configLocation) { + try { + URL configUrl = configLocation.getURL(); + String configFileName = configUrl.getPath(); + if (configFileName.endsWith(".yaml") || configFileName.endsWith(".yml")) { + return new YamlClientConfigBuilder(configUrl).build(); + } + return new XmlClientConfigBuilder(configUrl).build(); + } + catch (IOException ex) { + throw new UncheckedIOException("Failed to load Hazelcast config", ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java index 2017fe4332ff..56bac0150678 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java @@ -23,6 +23,7 @@ import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.nio.file.Files; +import java.util.Set; import com.hazelcast.client.HazelcastClient; import com.hazelcast.client.config.ClientConfig; @@ -150,6 +151,21 @@ void clientConfigTakesPrecedence() { .isInstanceOf(HazelcastClientProxy.class)); } + @Test + void connectionDetailsTakesPrecedenceOverConfigFile() { + this.contextRunner.withUserConfiguration(HazelcastConnectionDetailsConfig.class) + .withPropertyValues("spring.hazelcast.config=this-is-ignored.xml") + .run(assertSpecificHazelcastClient("connection-details")); + } + + @Test + void connectionDetailsTakesPrecedenceOverUserDefinedClientConfig() { + this.contextRunner + .withUserConfiguration(HazelcastConnectionDetailsConfig.class, HazelcastServerAndClientConfig.class) + .withPropertyValues("spring.hazelcast.config=this-is-ignored.xml") + .run(assertSpecificHazelcastClient("connection-details")); + } + @Test void clientConfigWithInstanceNameCreatesClientIfNecessary() throws MalformedURLException { assertThat(HazelcastClient.getHazelcastClientByName("spring-boot")).isNull(); @@ -202,6 +218,20 @@ private File prepareConfiguration(String input) { } } + @Configuration(proxyBeanMethods = false) + static class HazelcastConnectionDetailsConfig { + + @Bean + HazelcastConnectionDetails hazelcastConnectionDetails() { + ClientConfig config = new ClientConfig(); + config.setLabels(Set.of("connection-details")); + config.getConnectionStrategyConfig().getConnectionRetryConfig().setClusterConnectTimeoutMillis(60000); + config.getNetworkConfig().getAddresses().add(endpointAddress); + return () -> config; + } + + } + @Configuration(proxyBeanMethods = false) static class HazelcastServerAndClientConfig { diff --git a/spring-boot-project/spring-boot-docker-compose/build.gradle b/spring-boot-project/spring-boot-docker-compose/build.gradle index 6fffc61cc1a4..86566dbfb96b 100644 --- a/spring-boot-project/spring-boot-docker-compose/build.gradle +++ b/spring-boot-project/spring-boot-docker-compose/build.gradle @@ -12,12 +12,14 @@ dependencies { api(project(":spring-boot-project:spring-boot")) dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker")) + dockerTestImplementation("com.hazelcast:hazelcast") dockerTestImplementation("com.redis:testcontainers-redis") dockerTestImplementation("org.assertj:assertj-core") dockerTestImplementation("org.awaitility:awaitility") dockerTestImplementation("org.junit.jupiter:junit-jupiter") dockerTestImplementation("org.testcontainers:testcontainers") + dockerTestRuntimeOnly("com.microsoft.sqlserver:mssql-jdbc") dockerTestRuntimeOnly("com.oracle.database.r2dbc:oracle-r2dbc") dockerTestRuntimeOnly("io.r2dbc:r2dbc-mssql") @@ -33,6 +35,7 @@ dependencies { optional("org.mongodb:mongodb-driver-core") optional("org.neo4j.driver:neo4j-java-driver") optional("org.springframework.data:spring-data-r2dbc") + optional("com.hazelcast:hazelcast") testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-test")) diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..189f079edc26 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,66 @@ +/* + * 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.docker.compose.service.connection.hazelcast; + +import java.util.UUID; + +import com.hazelcast.client.HazelcastClient; +import com.hazelcast.client.config.ClientConfig; +import com.hazelcast.config.Config; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; + +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest; +import org.springframework.boot.testsupport.container.TestImage; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link HazelcastDockerComposeConnectionDetailsFactory}. + * + * @author Dmytro Nosan + */ +class HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests { + + @DockerComposeTest(composeFile = "hazelcast-compose.yaml", image = TestImage.HAZELCAST) + void runCreatesConnectionDetails(HazelcastConnectionDetails connectionDetails) { + ClientConfig config = connectionDetails.getClientConfig(); + assertThat(config.getClusterName()).isEqualTo(Config.DEFAULT_CLUSTER_NAME); + verifyConnection(config); + } + + @DockerComposeTest(composeFile = "hazelcast-cluster-name-compose.yaml", image = TestImage.HAZELCAST) + void runCreatesConnectionDetailsCustomClusterName(HazelcastConnectionDetails connectionDetails) { + ClientConfig config = connectionDetails.getClientConfig(); + assertThat(config.getClusterName()).isEqualTo("spring-boot"); + verifyConnection(config); + } + + private static void verifyConnection(ClientConfig config) { + HazelcastInstance hazelcastInstance = HazelcastClient.newHazelcastClient(config); + try { + IMap map = hazelcastInstance.getMap(UUID.randomUUID().toString()); + map.put("docker", "compose"); + assertThat(map.get("docker")).isEqualTo("compose"); + } + finally { + hazelcastInstance.shutdown(); + } + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-cluster-name-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-cluster-name-compose.yaml new file mode 100644 index 000000000000..fde817d73f4d --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-cluster-name-compose.yaml @@ -0,0 +1,7 @@ +services: + hazelcast: + image: '{imageName}' + environment: + HZ_CLUSTERNAME: "spring-boot" + ports: + - '5701' diff --git a/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-compose.yaml new file mode 100644 index 000000000000..89aaaaa9775d --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-compose.yaml @@ -0,0 +1,5 @@ +services: + hazelcast: + image: '{imageName}' + ports: + - '5701' diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactory.java new file mode 100644 index 000000000000..315a050c478a --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,76 @@ +/* + * 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.docker.compose.service.connection.hazelcast; + +import com.hazelcast.client.config.ClientConfig; + +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConnectionDetails; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create + * {@link HazelcastConnectionDetails} for a {@code hazelcast} service. + * + * @author Dmytro Nosan + */ +class HazelcastDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int DEFAULT_PORT = 5701; + + protected HazelcastDockerComposeConnectionDetailsFactory() { + super("hazelcast/hazelcast", "com.hazelcast.client.config.ClientConfig"); + } + + @Override + protected HazelcastConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new HazelcastDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link HazelcastConnectionDetails} backed by a {@code hazelcast} + * {@link RunningService}. + */ + static class HazelcastDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements HazelcastConnectionDetails { + + private final String host; + + private final int port; + + private final HazelcastEnvironment environment; + + HazelcastDockerComposeConnectionDetails(RunningService service) { + super(service); + this.host = service.host(); + this.port = service.ports().get(DEFAULT_PORT); + this.environment = new HazelcastEnvironment(service.env()); + } + + @Override + public ClientConfig getClientConfig() { + ClientConfig config = new ClientConfig(); + this.environment.getClusterName().ifPresent(config::setClusterName); + config.getNetworkConfig().addAddress(this.host + ":" + this.port); + return config; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironment.java new file mode 100644 index 000000000000..f1a503e0fa75 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironment.java @@ -0,0 +1,39 @@ +/* + * 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.docker.compose.service.connection.hazelcast; + +import java.util.Map; +import java.util.Optional; + +/** + * Hazelcast environment details. + * + * @author Dmytro Nosan + */ +class HazelcastEnvironment { + + private final String clusterName; + + HazelcastEnvironment(Map env) { + this.clusterName = env.get("HZ_CLUSTERNAME"); + } + + Optional getClusterName() { + return Optional.ofNullable(this.clusterName); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/package-info.java new file mode 100644 index 000000000000..b1798b8a14c4 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Auto-configuration for Docker Compose Hazelcast service connections. + */ +package org.springframework.boot.docker.compose.service.connection.hazelcast; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories index a532e257d139..12d13e5f65c4 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories @@ -11,6 +11,7 @@ org.springframework.boot.docker.compose.service.connection.activemq.ArtemisDocke org.springframework.boot.docker.compose.service.connection.cassandra.CassandraDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.elasticsearch.ElasticsearchDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.flyway.JdbcAdaptingFlywayConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.hazelcast.HazelcastDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.ldap.OpenLdapDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.liquibase.JdbcAdaptingLiquibaseConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.mariadb.MariaDbJdbcDockerComposeConnectionDetailsFactory,\ @@ -20,8 +21,8 @@ org.springframework.boot.docker.compose.service.connection.mysql.MySqlJdbcDocker org.springframework.boot.docker.compose.service.connection.mysql.MySqlR2dbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.neo4j.Neo4jDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.oracle.OracleFreeJdbcDockerComposeConnectionDetailsFactory,\ -org.springframework.boot.docker.compose.service.connection.oracle.OracleXeJdbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.oracle.OracleFreeR2dbcDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.oracle.OracleXeJdbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.oracle.OracleXeR2dbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.otlp.OpenTelemetryLoggingDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.otlp.OpenTelemetryMetricsDockerComposeConnectionDetailsFactory,\ diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironmentTests.java new file mode 100644 index 000000000000..2f53a18a02cb --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironmentTests.java @@ -0,0 +1,45 @@ +/* + * 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.docker.compose.service.connection.hazelcast; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HazelcastEnvironment}. + * + * @author Dmytro Nosan + */ +class HazelcastEnvironmentTests { + + @Test + void getClusterNameWhenHasNoHzClusterNameSet() { + HazelcastEnvironment environment = new HazelcastEnvironment(Collections.emptyMap()); + assertThat(environment.getClusterName()).isEmpty(); + } + + @Test + void getClusterNameWhenHzClusterNameSet() { + HazelcastEnvironment environment = new HazelcastEnvironment(Map.of("HZ_CLUSTERNAME", "spring-boot")); + assertThat(environment.getClusterName()).isNotEmpty().hasValue("spring-boot"); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc index f7e026d6aaf8..51cfebad4bd7 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc @@ -95,6 +95,9 @@ The following service connections are currently supported: | `ElasticsearchConnectionDetails` | Containers named "elasticsearch" or "bitnami/elasticsearch" +| `HazelcastConnectionDetails` +| Containers named "hazelcast/hazelcast". + | `JdbcConnectionDetails` | Containers named "gvenzl/oracle-free", "gvenzl/oracle-xe", "mariadb", "bitnami/mariadb", "mssql/server", "mysql", "bitnami/mysql", "postgres", or "bitnami/postgresql" diff --git a/spring-boot-project/spring-boot-testcontainers/build.gradle b/spring-boot-project/spring-boot-testcontainers/build.gradle index 204969663955..80be519f5478 100644 --- a/spring-boot-project/spring-boot-testcontainers/build.gradle +++ b/spring-boot-project/spring-boot-testcontainers/build.gradle @@ -20,6 +20,7 @@ dependencies { exclude group: "commons-logging", module: "commons-logging" } dockerTestImplementation("com.couchbase.client:java-client") + dockerTestImplementation("com.hazelcast:hazelcast") dockerTestImplementation("io.micrometer:micrometer-registry-otlp") dockerTestImplementation("io.rest-assured:rest-assured") { exclude group: "commons-logging", module: "commons-logging" @@ -81,6 +82,7 @@ dependencies { optional("org.testcontainers:redpanda") optional("org.testcontainers:r2dbc") optional("com.redis:testcontainers-redis") + optional("com.hazelcast:hazelcast") testImplementation(project(":spring-boot-project:spring-boot-test")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..e97ecc247503 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,86 @@ +/* + * 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.service.connection.hazelcast; + +import java.util.UUID; +import java.util.function.Consumer; + +import com.hazelcast.client.config.ClientConfig; +import com.hazelcast.client.impl.clientside.HazelcastClientProxy; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.HazelcastContainer; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HazelcastContainerConnectionDetailsFactory} with a custom hazelcast + * cluster name. + * + * @author Dmytro Nosan + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final HazelcastContainer hazelcast = TestImage.container(HazelcastContainer.class) + .withEnv("HZ_CLUSTERNAME", "spring-boot"); + + @Autowired(required = false) + private HazelcastConnectionDetails connectionDetails; + + @Autowired + private HazelcastInstance hazelcastInstance; + + @Test + void connectionCanBeMadeToHazelcastContainer() { + assertThat(this.connectionDetails).isNotNull(); + assertThat(this.hazelcastInstance).satisfies(clusterName("spring-boot")); + IMap map = this.hazelcastInstance.getMap(UUID.randomUUID().toString()); + map.put("test", "containers"); + assertThat(map.get("test")).isEqualTo("containers"); + } + + private static Consumer clusterName(String name) { + return (hazelcastInstance) -> { + assertThat(hazelcastInstance).isInstanceOf(HazelcastClientProxy.class); + HazelcastClientProxy proxy = (HazelcastClientProxy) hazelcastInstance; + assertThat(proxy.getClientConfig()).extracting(ClientConfig::getClusterName).isEqualTo(name); + }; + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(HazelcastAutoConfiguration.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 000000000000..876b9f1e8bb7 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,72 @@ +/* + * 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.service.connection.hazelcast; + +import java.util.UUID; + +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.testsupport.container.HazelcastContainer; +import org.springframework.boot.testsupport.container.TestImage; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HazelcastContainerConnectionDetailsFactory}. + * + * @author Dmytro Nosan + */ +@SpringJUnitConfig +@Testcontainers(disabledWithoutDocker = true) +class HazelcastContainerConnectionDetailsFactoryIntegrationTests { + + @Container + @ServiceConnection + static final HazelcastContainer hazelcast = TestImage.container(HazelcastContainer.class); + + @Autowired(required = false) + private HazelcastConnectionDetails connectionDetails; + + @Autowired + private HazelcastInstance hazelcastInstance; + + @Test + void connectionCanBeMadeToHazelcastContainer() { + assertThat(this.connectionDetails).isNotNull(); + IMap map = this.hazelcastInstance.getMap(UUID.randomUUID().toString()); + map.put("test", "containers"); + assertThat(map.get("test")).isEqualTo("containers"); + } + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(HazelcastAutoConfiguration.class) + static class TestConfiguration { + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactory.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactory.java new file mode 100644 index 000000000000..19486f133b74 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactory.java @@ -0,0 +1,76 @@ +/* + * 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.service.connection.hazelcast; + +import java.util.Map; +import java.util.Optional; + +import com.hazelcast.client.config.ClientConfig; +import org.testcontainers.containers.Container; +import org.testcontainers.containers.GenericContainer; + +import org.springframework.boot.autoconfigure.hazelcast.HazelcastConnectionDetails; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; +import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +/** + * {@link ContainerConnectionDetailsFactory} to create {@link HazelcastConnectionDetails} + * from a {@link ServiceConnection @ServiceConnection}-annotated {@link GenericContainer} + * using the {@code "hazelcast/hazelcast"} image. + * + * @author Dmytro Nosan + */ +class HazelcastContainerConnectionDetailsFactory + extends ContainerConnectionDetailsFactory, HazelcastConnectionDetails> { + + private static final int DEFAULT_PORT = 5701; + + private static final String CLUSTER_NAME_ENV = "HZ_CLUSTERNAME"; + + HazelcastContainerConnectionDetailsFactory() { + super("hazelcast/hazelcast", "com.hazelcast.client.config.ClientConfig"); + } + + @Override + protected HazelcastConnectionDetails getContainerConnectionDetails(ContainerConnectionSource> source) { + return new HazelcastContainerConnectionDetails(source); + } + + /** + * {@link HazelcastConnectionDetails} backed by a {@link ContainerConnectionSource}. + */ + private static final class HazelcastContainerConnectionDetails extends ContainerConnectionDetails> + implements HazelcastConnectionDetails { + + private HazelcastContainerConnectionDetails(ContainerConnectionSource> source) { + super(source); + } + + @Override + public ClientConfig getClientConfig() { + ClientConfig config = new ClientConfig(); + Container container = getContainer(); + Map env = container.getEnvMap(); + Optional.ofNullable(env.get(CLUSTER_NAME_ENV)).ifPresent(config::setClusterName); + config.getNetworkConfig().addAddress(container.getHost() + ":" + container.getMappedPort(DEFAULT_PORT)); + return config; + } + + } + +} diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/package-info.java b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/package-info.java new file mode 100644 index 000000000000..f5113a8902d4 --- /dev/null +++ b/spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Support for testcontainers Hazelcast service connections. + */ +package org.springframework.boot.testcontainers.service.connection.hazelcast; diff --git a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories index bd92b9665f14..c556ff49eb80 100644 --- a/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories @@ -14,8 +14,9 @@ org.springframework.boot.testcontainers.service.connection.activemq.ArtemisConta org.springframework.boot.testcontainers.service.connection.amqp.RabbitContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.cassandra.CassandraContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.couchbase.CouchbaseContainerConnectionDetailsFactory,\ -org.springframework.boot.testcontainers.service.connection.flyway.FlywayContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.elasticsearch.ElasticsearchContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.flyway.FlywayContainerConnectionDetailsFactory,\ +org.springframework.boot.testcontainers.service.connection.hazelcast.HazelcastContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.jdbc.JdbcContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.kafka.ApacheKafkaContainerConnectionDetailsFactory,\ org.springframework.boot.testcontainers.service.connection.kafka.ConfluentKafkaContainerConnectionDetailsFactory,\ diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/HazelcastContainer.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/HazelcastContainer.java new file mode 100644 index 000000000000..0751d6475afc --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/HazelcastContainer.java @@ -0,0 +1,36 @@ +/* + * 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.testsupport.container; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +/** + * A {@link GenericContainer} for Hazelcast. + * + * @author Dmytro Nosan + */ +public final class HazelcastContainer extends GenericContainer { + + private static final int DEFAULT_PORT = 5701; + + public HazelcastContainer(DockerImageName dockerImageName) { + super(dockerImageName); + addExposedPorts(DEFAULT_PORT); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/TestImage.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/TestImage.java index 3e19298cc899..96daef3968a2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/TestImage.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/TestImage.java @@ -109,6 +109,11 @@ public enum TestImage { GRAFANA_OTEL_LGTM("grafana/otel-lgtm", "0.6.0", () -> LgtmStackContainer.class, (container) -> ((LgtmStackContainer) container).withStartupTimeout(Duration.ofMinutes(2))), + /** + * A container image suitable for testing Hazelcast. + */ + HAZELCAST("hazelcast/hazelcast", "5.5.0-slim", () -> HazelcastContainer.class), + /** * A container image suitable for testing Confluent's distribution of Kafka. */