Browse Source

Add service connection support for Hazelcast

See gh-42416
pull/42459/head
Dmytro Nosan 1 year ago committed by Moritz Halbritter
parent
commit
cee7439dbe
  1. 43
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java
  2. 46
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientInstanceConfiguration.java
  3. 37
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetails.java
  4. 68
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/PropertiesHazelcastConnectionDetails.java
  5. 30
      spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java
  6. 3
      spring-boot-project/spring-boot-docker-compose/build.gradle
  7. 66
      spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests.java
  8. 7
      spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-cluster-name-compose.yaml
  9. 5
      spring-boot-project/spring-boot-docker-compose/src/dockerTest/resources/org/springframework/boot/docker/compose/service/connection/hazelcast/hazelcast-compose.yaml
  10. 76
      spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactory.java
  11. 39
      spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironment.java
  12. 20
      spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/package-info.java
  13. 3
      spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories
  14. 45
      spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironmentTests.java
  15. 3
      spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/dev-services.adoc
  16. 2
      spring-boot-project/spring-boot-testcontainers/build.gradle
  17. 86
      spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests.java
  18. 72
      spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryIntegrationTests.java
  19. 76
      spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactory.java
  20. 20
      spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/package-info.java
  21. 3
      spring-boot-project/spring-boot-testcontainers/src/main/resources/META-INF/spring.factories
  22. 36
      spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/HazelcastContainer.java
  23. 5
      spring-boot-project/spring-boot-tools/spring-boot-test-support-docker/src/main/java/org/springframework/boot/testsupport/container/TestImage.java

43
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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.boot.autoconfigure.condition.ConditionalOnSingleCandi @@ -31,9 +26,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandi
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 @@ import org.springframework.util.StringUtils; @@ -44,49 +38,32 @@ import org.springframework.util.StringUtils;
@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;
}
}

46
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientInstanceConfiguration.java

@ -0,0 +1,46 @@ @@ -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);
}
}

37
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConnectionDetails.java

@ -0,0 +1,37 @@ @@ -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();
}

68
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/PropertiesHazelcastConnectionDetails.java

@ -0,0 +1,68 @@ @@ -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);
}
}
}

30
spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java

@ -23,6 +23,7 @@ import java.io.IOException; @@ -23,6 +23,7 @@ import java.io.IOException;
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 @@ class HazelcastAutoConfigurationClientTests { @@ -150,6 +151,21 @@ class HazelcastAutoConfigurationClientTests {
.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 @@ class HazelcastAutoConfigurationClientTests { @@ -202,6 +218,20 @@ class HazelcastAutoConfigurationClientTests {
}
}
@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 {

3
spring-boot-project/spring-boot-docker-compose/build.gradle

@ -12,12 +12,14 @@ dependencies { @@ -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 { @@ -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"))

66
spring-boot-project/spring-boot-docker-compose/src/dockerTest/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests.java

@ -0,0 +1,66 @@ @@ -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<String, String> map = hazelcastInstance.getMap(UUID.randomUUID().toString());
map.put("docker", "compose");
assertThat(map.get("docker")).isEqualTo("compose");
}
finally {
hazelcastInstance.shutdown();
}
}
}

7
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 @@ @@ -0,0 +1,7 @@
services:
hazelcast:
image: '{imageName}'
environment:
HZ_CLUSTERNAME: "spring-boot"
ports:
- '5701'

5
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 @@ @@ -0,0 +1,5 @@
services:
hazelcast:
image: '{imageName}'
ports:
- '5701'

76
spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastDockerComposeConnectionDetailsFactory.java

@ -0,0 +1,76 @@ @@ -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<HazelcastConnectionDetails> {
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;
}
}
}

39
spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironment.java

@ -0,0 +1,39 @@ @@ -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<String, String> env) {
this.clusterName = env.get("HZ_CLUSTERNAME");
}
Optional<String> getClusterName() {
return Optional.ofNullable(this.clusterName);
}
}

20
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 @@ @@ -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;

3
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 @@ -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 @@ -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,\

45
spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/hazelcast/HazelcastEnvironmentTests.java

@ -0,0 +1,45 @@ @@ -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");
}
}

3
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: @@ -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"

2
spring-boot-project/spring-boot-testcontainers/build.gradle

@ -20,6 +20,7 @@ dependencies { @@ -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 { @@ -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"))

86
spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/CustomClusterNameHazelcastContainerConnectionDetailsFactoryIntegrationTests.java

@ -0,0 +1,86 @@ @@ -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<String, String> map = this.hazelcastInstance.getMap(UUID.randomUUID().toString());
map.put("test", "containers");
assertThat(map.get("test")).isEqualTo("containers");
}
private static Consumer<HazelcastInstance> 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 {
}
}

72
spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactoryIntegrationTests.java

@ -0,0 +1,72 @@ @@ -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<String, String> 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 {
}
}

76
spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/HazelcastContainerConnectionDetailsFactory.java

@ -0,0 +1,76 @@ @@ -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<Container<?>, 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<Container<?>> source) {
return new HazelcastContainerConnectionDetails(source);
}
/**
* {@link HazelcastConnectionDetails} backed by a {@link ContainerConnectionSource}.
*/
private static final class HazelcastContainerConnectionDetails extends ContainerConnectionDetails<Container<?>>
implements HazelcastConnectionDetails {
private HazelcastContainerConnectionDetails(ContainerConnectionSource<Container<?>> source) {
super(source);
}
@Override
public ClientConfig getClientConfig() {
ClientConfig config = new ClientConfig();
Container<?> container = getContainer();
Map<String, String> env = container.getEnvMap();
Optional.ofNullable(env.get(CLUSTER_NAME_ENV)).ifPresent(config::setClusterName);
config.getNetworkConfig().addAddress(container.getHost() + ":" + container.getMappedPort(DEFAULT_PORT));
return config;
}
}
}

20
spring-boot-project/spring-boot-testcontainers/src/main/java/org/springframework/boot/testcontainers/service/connection/hazelcast/package-info.java

@ -0,0 +1,20 @@ @@ -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;

3
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 @@ -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,\

36
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 @@ @@ -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<HazelcastContainer> {
private static final int DEFAULT_PORT = 5701;
public HazelcastContainer(DockerImageName dockerImageName) {
super(dockerImageName);
addExposedPorts(DEFAULT_PORT);
}
}

5
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 { @@ -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.
*/

Loading…
Cancel
Save