Skip to content

Commit cfb416c

Browse files
committed
Provide service connection support for Hazelcast
1 parent c7c73be commit cfb416c

File tree

19 files changed

+548
-36
lines changed

19 files changed

+548
-36
lines changed

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

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,13 +16,8 @@
1616

1717
package org.springframework.boot.autoconfigure.hazelcast;
1818

19-
import java.io.IOException;
20-
import java.net.URL;
21-
2219
import com.hazelcast.client.HazelcastClient;
2320
import com.hazelcast.client.config.ClientConfig;
24-
import com.hazelcast.client.config.XmlClientConfigBuilder;
25-
import com.hazelcast.client.config.YamlClientConfigBuilder;
2621
import com.hazelcast.core.HazelcastInstance;
2722

2823
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -31,9 +26,8 @@
3126
import org.springframework.context.annotation.Bean;
3227
import org.springframework.context.annotation.Conditional;
3328
import org.springframework.context.annotation.Configuration;
34-
import org.springframework.core.io.Resource;
29+
import org.springframework.context.annotation.Import;
3530
import org.springframework.core.io.ResourceLoader;
36-
import org.springframework.util.StringUtils;
3731

3832
/**
3933
* Configuration for Hazelcast client.
@@ -44,49 +38,32 @@
4438
@Configuration(proxyBeanMethods = false)
4539
@ConditionalOnClass(HazelcastClient.class)
4640
@ConditionalOnMissingBean(HazelcastInstance.class)
41+
@Import(HazelcastClientInstanceConfiguration.class)
4742
class HazelcastClientConfiguration {
4843

4944
static final String CONFIG_SYSTEM_PROPERTY = "hazelcast.client.config";
5045

51-
private static HazelcastInstance getHazelcastInstance(ClientConfig config) {
52-
if (StringUtils.hasText(config.getInstanceName())) {
53-
return HazelcastClient.getOrCreateHazelcastClient(config);
54-
}
55-
return HazelcastClient.newHazelcastClient(config);
56-
}
57-
5846
@Configuration(proxyBeanMethods = false)
59-
@ConditionalOnMissingBean(ClientConfig.class)
47+
@ConditionalOnMissingBean({ ClientConfig.class, HazelcastConnectionDetails.class })
6048
@Conditional(HazelcastClientConfigAvailableCondition.class)
6149
static class HazelcastClientConfigFileConfiguration {
6250

6351
@Bean
64-
HazelcastInstance hazelcastInstance(HazelcastProperties properties, ResourceLoader resourceLoader)
65-
throws IOException {
66-
Resource configLocation = properties.resolveConfigLocation();
67-
ClientConfig config = (configLocation != null) ? loadClientConfig(configLocation) : ClientConfig.load();
68-
config.setClassLoader(resourceLoader.getClassLoader());
69-
return getHazelcastInstance(config);
70-
}
71-
72-
private ClientConfig loadClientConfig(Resource configLocation) throws IOException {
73-
URL configUrl = configLocation.getURL();
74-
String configFileName = configUrl.getPath();
75-
if (configFileName.endsWith(".yaml") || configFileName.endsWith(".yml")) {
76-
return new YamlClientConfigBuilder(configUrl).build();
77-
}
78-
return new XmlClientConfigBuilder(configUrl).build();
52+
HazelcastConnectionDetails hazelcastConnectionDetails(HazelcastProperties properties,
53+
ResourceLoader resourceLoader) {
54+
return new PropertiesHazelcastConnectionDetails(properties, resourceLoader);
7955
}
8056

8157
}
8258

8359
@Configuration(proxyBeanMethods = false)
60+
@ConditionalOnMissingBean(HazelcastConnectionDetails.class)
8461
@ConditionalOnSingleCandidate(ClientConfig.class)
8562
static class HazelcastClientConfigConfiguration {
8663

8764
@Bean
88-
HazelcastInstance hazelcastInstance(ClientConfig config) {
89-
return getHazelcastInstance(config);
65+
HazelcastConnectionDetails hazelcastConnectionDetails(ClientConfig config) {
66+
return () -> config;
9067
}
9168

9269
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.hazelcast;
18+
19+
import com.hazelcast.client.HazelcastClient;
20+
import com.hazelcast.client.config.ClientConfig;
21+
import com.hazelcast.core.HazelcastInstance;
22+
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.util.StringUtils;
27+
28+
/**
29+
* Configuration for Hazelcast client instance.
30+
*
31+
* @author Dmytro Nosan
32+
*/
33+
@Configuration(proxyBeanMethods = false)
34+
@ConditionalOnBean(HazelcastConnectionDetails.class)
35+
class HazelcastClientInstanceConfiguration {
36+
37+
@Bean
38+
HazelcastInstance hazelcastInstance(HazelcastConnectionDetails hazelcastConnectionDetails) {
39+
ClientConfig config = hazelcastConnectionDetails.getClientConfig();
40+
if (StringUtils.hasText(config.getInstanceName())) {
41+
return HazelcastClient.getOrCreateHazelcastClient(config);
42+
}
43+
return HazelcastClient.newHazelcastClient(config);
44+
}
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.hazelcast;
18+
19+
import com.hazelcast.client.config.ClientConfig;
20+
21+
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
22+
23+
/**
24+
* Details required to establish a client connection to a Hazelcast instance.
25+
*
26+
* @author Dmytro Nosan
27+
* @since 3.4.0
28+
*/
29+
public interface HazelcastConnectionDetails extends ConnectionDetails {
30+
31+
/**
32+
* The {@link ClientConfig} for Hazelcast client.
33+
* @return the client config
34+
*/
35+
ClientConfig getClientConfig();
36+
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.hazelcast;
18+
19+
import java.io.IOException;
20+
import java.io.UncheckedIOException;
21+
import java.net.URL;
22+
23+
import com.hazelcast.client.config.ClientConfig;
24+
import com.hazelcast.client.config.XmlClientConfigBuilder;
25+
import com.hazelcast.client.config.YamlClientConfigBuilder;
26+
27+
import org.springframework.core.io.Resource;
28+
import org.springframework.core.io.ResourceLoader;
29+
30+
/**
31+
* Adapts {@link HazelcastProperties} to {@link HazelcastConnectionDetails}.
32+
*
33+
* @author Dmytro Nosan
34+
*/
35+
class PropertiesHazelcastConnectionDetails implements HazelcastConnectionDetails {
36+
37+
private final HazelcastProperties properties;
38+
39+
private final ResourceLoader resourceLoader;
40+
41+
PropertiesHazelcastConnectionDetails(HazelcastProperties properties, ResourceLoader resourceLoader) {
42+
this.properties = properties;
43+
this.resourceLoader = resourceLoader;
44+
}
45+
46+
@Override
47+
public ClientConfig getClientConfig() {
48+
Resource configLocation = this.properties.resolveConfigLocation();
49+
ClientConfig config = (configLocation != null) ? loadClientConfig(configLocation) : ClientConfig.load();
50+
config.setClassLoader(this.resourceLoader.getClassLoader());
51+
return config;
52+
}
53+
54+
private ClientConfig loadClientConfig(Resource configLocation) {
55+
try {
56+
URL configUrl = configLocation.getURL();
57+
String configFileName = configUrl.getPath();
58+
if (configFileName.endsWith(".yaml") || configFileName.endsWith(".yml")) {
59+
return new YamlClientConfigBuilder(configUrl).build();
60+
}
61+
return new XmlClientConfigBuilder(configUrl).build();
62+
}
63+
catch (IOException ex) {
64+
throw new UncheckedIOException("Failed to load Hazelcast config", ex);
65+
}
66+
}
67+
68+
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,17 @@ void clientConfigTakesPrecedence() {
150150
.isInstanceOf(HazelcastClientProxy.class));
151151
}
152152

153+
@Test
154+
void connectionDetailsTakesPrecedence() {
155+
this.contextRunner
156+
.withUserConfiguration(HazelcastConnectionDetailsConfig.class, HazelcastServerAndClientConfig.class)
157+
.withPropertyValues("spring.hazelcast.config=this-is-ignored.xml")
158+
.run((context) -> assertThat(context).getBean(HazelcastInstance.class)
159+
.isInstanceOf(HazelcastClientProxy.class)
160+
.extracting(HazelcastInstance::getName)
161+
.isEqualTo("connection-details"));
162+
}
163+
153164
@Test
154165
void clientConfigWithInstanceNameCreatesClientIfNecessary() throws MalformedURLException {
155166
assertThat(HazelcastClient.getHazelcastClientByName("spring-boot")).isNull();
@@ -202,6 +213,20 @@ private File prepareConfiguration(String input) {
202213
}
203214
}
204215

216+
@Configuration(proxyBeanMethods = false)
217+
static class HazelcastConnectionDetailsConfig {
218+
219+
@Bean
220+
HazelcastConnectionDetails hazelcastConnectionDetails() {
221+
ClientConfig config = new ClientConfig();
222+
config.setInstanceName("connection-details");
223+
config.getConnectionStrategyConfig().getConnectionRetryConfig().setClusterConnectTimeoutMillis(60000);
224+
config.getNetworkConfig().getAddresses().add(endpointAddress);
225+
return () -> config;
226+
}
227+
228+
}
229+
205230
@Configuration(proxyBeanMethods = false)
206231
static class HazelcastServerAndClientConfig {
207232

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ dependencies {
1818
dockerTestImplementation("org.awaitility:awaitility")
1919
dockerTestImplementation("org.junit.jupiter:junit-jupiter")
2020
dockerTestImplementation("org.testcontainers:testcontainers")
21+
dockerTestImplementation("com.hazelcast:hazelcast")
22+
2123

2224
dockerTestRuntimeOnly("com.microsoft.sqlserver:mssql-jdbc")
2325
dockerTestRuntimeOnly("com.oracle.database.r2dbc:oracle-r2dbc")
@@ -34,6 +36,7 @@ dependencies {
3436
optional("org.mongodb:mongodb-driver-core")
3537
optional("org.neo4j.driver:neo4j-java-driver")
3638
optional("org.springframework.data:spring-data-r2dbc")
39+
optional("com.hazelcast:hazelcast")
3740

3841
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
3942
testImplementation(project(":spring-boot-project:spring-boot-test"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docker.compose.service.connection.hazelcast;
18+
19+
import com.hazelcast.client.HazelcastClient;
20+
import com.hazelcast.core.HazelcastInstance;
21+
import com.hazelcast.map.IMap;
22+
23+
import org.springframework.boot.autoconfigure.hazelcast.HazelcastConnectionDetails;
24+
import org.springframework.boot.docker.compose.service.connection.test.DockerComposeTest;
25+
import org.springframework.boot.testsupport.container.TestImage;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
29+
/**
30+
* Integration tests for {@link HazelcastDockerComposeConnectionDetailsFactory}.
31+
*
32+
* @author Dmytro Nosan
33+
*/
34+
class HazelcastDockerComposeConnectionDetailsFactoryIntegrationTests {
35+
36+
@DockerComposeTest(composeFile = "hazelcast-compose.yaml", image = TestImage.HAZELCAST)
37+
void runCreatesConnectionDetails(HazelcastConnectionDetails connectionDetails) {
38+
HazelcastInstance hazelcastInstance = HazelcastClient.newHazelcastClient(connectionDetails.getClientConfig());
39+
try {
40+
IMap<Object, Object> map = hazelcastInstance.getMap("spring-boot");
41+
map.put("docker", "compose");
42+
assertThat(map.get("docker")).isEqualTo("compose");
43+
}
44+
finally {
45+
hazelcastInstance.shutdown();
46+
}
47+
48+
}
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
services:
2+
hazelcast:
3+
image: '{imageName}'
4+
ports:
5+
- '5701'

0 commit comments

Comments
 (0)