Skip to content

Commit a9f9d56

Browse files
committed
Merge pull request #42416 from nosan
* pr/42416: Polish "Add service connection support for Hazelcast" Add service connection support for Hazelcast Closes gh-42416
2 parents fb3dd68 + 33def6d commit a9f9d56

File tree

24 files changed

+822
-58
lines changed

24 files changed

+822
-58
lines changed
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,24 +16,13 @@
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;
23-
import com.hazelcast.client.config.ClientConfig;
24-
import com.hazelcast.client.config.XmlClientConfigBuilder;
25-
import com.hazelcast.client.config.YamlClientConfigBuilder;
2620
import com.hazelcast.core.HazelcastInstance;
2721

2822
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2923
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
30-
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
31-
import org.springframework.context.annotation.Bean;
32-
import org.springframework.context.annotation.Conditional;
3324
import org.springframework.context.annotation.Configuration;
34-
import org.springframework.core.io.Resource;
35-
import org.springframework.core.io.ResourceLoader;
36-
import org.springframework.util.StringUtils;
25+
import org.springframework.context.annotation.Import;
3726

3827
/**
3928
* Configuration for Hazelcast client.
@@ -44,51 +33,9 @@
4433
@Configuration(proxyBeanMethods = false)
4534
@ConditionalOnClass(HazelcastClient.class)
4635
@ConditionalOnMissingBean(HazelcastInstance.class)
36+
@Import({ HazelcastConnectionDetailsConfiguration.class, HazelcastClientInstanceConfiguration.class })
4737
class HazelcastClientConfiguration {
4838

4939
static final String CONFIG_SYSTEM_PROPERTY = "hazelcast.client.config";
5040

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-
58-
@Configuration(proxyBeanMethods = false)
59-
@ConditionalOnMissingBean(ClientConfig.class)
60-
@Conditional(HazelcastClientConfigAvailableCondition.class)
61-
static class HazelcastClientConfigFileConfiguration {
62-
63-
@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();
79-
}
80-
81-
}
82-
83-
@Configuration(proxyBeanMethods = false)
84-
@ConditionalOnSingleCandidate(ClientConfig.class)
85-
static class HazelcastClientConfigConfiguration {
86-
87-
@Bean
88-
HazelcastInstance hazelcastInstance(ClientConfig config) {
89-
return getHazelcastInstance(config);
90-
}
91-
92-
}
93-
9441
}
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,62 @@
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.condition.ConditionalOnMissingBean;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Conditional;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.core.io.ResourceLoader;
27+
28+
/**
29+
* {@link Configuration} for providing {@link HazelcastConnectionDetails}.
30+
*
31+
* @author Dmytro Nosan
32+
* @author Moritz Halbritter
33+
*/
34+
@Configuration(proxyBeanMethods = false)
35+
class HazelcastConnectionDetailsConfiguration {
36+
37+
@Configuration(proxyBeanMethods = false)
38+
@ConditionalOnMissingBean({ ClientConfig.class, HazelcastConnectionDetails.class })
39+
@Conditional(HazelcastClientConfigAvailableCondition.class)
40+
static class HazelcastClientConfigFileConfiguration {
41+
42+
@Bean
43+
HazelcastConnectionDetails hazelcastConnectionDetails(HazelcastProperties properties,
44+
ResourceLoader resourceLoader) {
45+
return new PropertiesHazelcastConnectionDetails(properties, resourceLoader);
46+
}
47+
48+
}
49+
50+
@Configuration(proxyBeanMethods = false)
51+
@ConditionalOnMissingBean(HazelcastConnectionDetails.class)
52+
@ConditionalOnSingleCandidate(ClientConfig.class)
53+
static class HazelcastClientConfigConfiguration {
54+
55+
@Bean
56+
HazelcastConnectionDetails hazelcastConnectionDetails(ClientConfig config) {
57+
return () -> config;
58+
}
59+
60+
}
61+
62+
}
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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.net.InetSocketAddress;
2424
import java.net.MalformedURLException;
2525
import java.nio.file.Files;
26+
import java.util.Set;
2627

2728
import com.hazelcast.client.HazelcastClient;
2829
import com.hazelcast.client.config.ClientConfig;
@@ -150,6 +151,21 @@ void clientConfigTakesPrecedence() {
150151
.isInstanceOf(HazelcastClientProxy.class));
151152
}
152153

154+
@Test
155+
void connectionDetailsTakesPrecedenceOverConfigFile() {
156+
this.contextRunner.withUserConfiguration(HazelcastConnectionDetailsConfig.class)
157+
.withPropertyValues("spring.hazelcast.config=this-is-ignored.xml")
158+
.run(assertSpecificHazelcastClient("connection-details"));
159+
}
160+
161+
@Test
162+
void connectionDetailsTakesPrecedenceOverUserDefinedClientConfig() {
163+
this.contextRunner
164+
.withUserConfiguration(HazelcastConnectionDetailsConfig.class, HazelcastServerAndClientConfig.class)
165+
.withPropertyValues("spring.hazelcast.config=this-is-ignored.xml")
166+
.run(assertSpecificHazelcastClient("connection-details"));
167+
}
168+
153169
@Test
154170
void clientConfigWithInstanceNameCreatesClientIfNecessary() throws MalformedURLException {
155171
assertThat(HazelcastClient.getHazelcastClientByName("spring-boot")).isNull();
@@ -202,6 +218,20 @@ private File prepareConfiguration(String input) {
202218
}
203219
}
204220

221+
@Configuration(proxyBeanMethods = false)
222+
static class HazelcastConnectionDetailsConfig {
223+
224+
@Bean
225+
HazelcastConnectionDetails hazelcastConnectionDetails() {
226+
ClientConfig config = new ClientConfig();
227+
config.setLabels(Set.of("connection-details"));
228+
config.getConnectionStrategyConfig().getConnectionRetryConfig().setClusterConnectTimeoutMillis(60000);
229+
config.getNetworkConfig().getAddresses().add(endpointAddress);
230+
return () -> config;
231+
}
232+
233+
}
234+
205235
@Configuration(proxyBeanMethods = false)
206236
static class HazelcastServerAndClientConfig {
207237

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies {
1212
api(project(":spring-boot-project:spring-boot"))
1313

1414
dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker"))
15+
dockerTestImplementation("com.hazelcast:hazelcast")
1516
dockerTestImplementation("com.redis:testcontainers-redis")
1617
dockerTestImplementation("org.assertj:assertj-core")
1718
dockerTestImplementation("org.awaitility:awaitility")
@@ -29,6 +30,7 @@ dependencies {
2930

3031
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
3132
optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
33+
optional("com.hazelcast:hazelcast")
3234
optional("io.r2dbc:r2dbc-spi")
3335
optional("org.mongodb:mongodb-driver-core")
3436
optional("org.neo4j.driver:neo4j-java-driver")

0 commit comments

Comments
 (0)