Skip to content

Commit 4307fdc

Browse files
wilkinsonamhalbritterphilwebb
committed
Add ConnectionDetail support to Cassandra auto-configuration
Update Cassandra auto-configuration so that `CassandraConnectionDetails` beans may be optionally used to provide connection details. See gh-34657 Co-Authored-By: Mortitz Halbritter <[email protected]> Co-Authored-By: Phillip Webb <[email protected]>
1 parent 61e9fe8 commit 4307fdc

File tree

3 files changed

+235
-64
lines changed

3 files changed

+235
-64
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cassandra/CassandraAutoConfiguration.java

Lines changed: 114 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.LinkedHashMap;
2424
import java.util.List;
2525
import java.util.Map;
26-
import java.util.function.Supplier;
2726

2827
import javax.net.ssl.SSLContext;
2928

@@ -64,10 +63,13 @@
6463
* @author Stephane Nicoll
6564
* @author Steffen F. Qvistgaard
6665
* @author Ittay Stern
66+
* @author Moritz Halbritter
67+
* @author Andy Wilkinson
68+
* @author Phillip Webb
6769
* @since 1.3.0
6870
*/
6971
@AutoConfiguration
70-
@ConditionalOnClass({ CqlSession.class })
72+
@ConditionalOnClass(CqlSession.class)
7173
@EnableConfigurationProperties(CassandraProperties.class)
7274
public class CassandraAutoConfiguration {
7375

@@ -80,6 +82,17 @@ public class CassandraAutoConfiguration {
8082
SPRING_BOOT_DEFAULTS = options.build();
8183
}
8284

85+
private final CassandraProperties properties;
86+
87+
private final CassandraConnectionDetails connectionDetails;
88+
89+
CassandraAutoConfiguration(CassandraProperties properties,
90+
ObjectProvider<CassandraConnectionDetails> connectionDetails) {
91+
this.properties = properties;
92+
this.connectionDetails = connectionDetails
93+
.getIfAvailable(() -> new PropertiesCassandraConnectionDetails(properties));
94+
}
95+
8396
@Bean
8497
@ConditionalOnMissingBean
8598
@Lazy
@@ -90,24 +103,25 @@ public CqlSession cassandraSession(CqlSessionBuilder cqlSessionBuilder) {
90103
@Bean
91104
@ConditionalOnMissingBean
92105
@Scope("prototype")
93-
public CqlSessionBuilder cassandraSessionBuilder(CassandraProperties properties,
94-
DriverConfigLoader driverConfigLoader, ObjectProvider<CqlSessionBuilderCustomizer> builderCustomizers) {
106+
public CqlSessionBuilder cassandraSessionBuilder(DriverConfigLoader driverConfigLoader,
107+
ObjectProvider<CqlSessionBuilderCustomizer> builderCustomizers) {
95108
CqlSessionBuilder builder = CqlSession.builder().withConfigLoader(driverConfigLoader);
96-
configureAuthentication(properties, builder);
97-
configureSsl(properties, builder);
98-
builder.withKeyspace(properties.getKeyspaceName());
109+
configureAuthentication(builder);
110+
configureSsl(builder);
111+
builder.withKeyspace(this.properties.getKeyspaceName());
99112
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
100113
return builder;
101114
}
102115

103-
private void configureAuthentication(CassandraProperties properties, CqlSessionBuilder builder) {
104-
if (properties.getUsername() != null) {
105-
builder.withAuthCredentials(properties.getUsername(), properties.getPassword());
116+
private void configureAuthentication(CqlSessionBuilder builder) {
117+
String username = this.connectionDetails.getUsername();
118+
if (username != null) {
119+
builder.withAuthCredentials(username, this.connectionDetails.getPassword());
106120
}
107121
}
108122

109-
private void configureSsl(CassandraProperties properties, CqlSessionBuilder builder) {
110-
if (properties.isSsl()) {
123+
private void configureSsl(CqlSessionBuilder builder) {
124+
if (this.connectionDetails instanceof PropertiesCassandraConnectionDetails && this.properties.isSsl()) {
111125
try {
112126
builder.withSslContext(SSLContext.getDefault());
113127
}
@@ -119,20 +133,20 @@ private void configureSsl(CassandraProperties properties, CqlSessionBuilder buil
119133

120134
@Bean(destroyMethod = "")
121135
@ConditionalOnMissingBean
122-
public DriverConfigLoader cassandraDriverConfigLoader(CassandraProperties properties,
136+
public DriverConfigLoader cassandraDriverConfigLoader(
123137
ObjectProvider<DriverConfigLoaderBuilderCustomizer> builderCustomizers) {
124138
ProgrammaticDriverConfigLoaderBuilder builder = new DefaultProgrammaticDriverConfigLoaderBuilder(
125-
() -> cassandraConfiguration(properties), DefaultDriverConfigLoader.DEFAULT_ROOT_PATH);
139+
() -> cassandraConfiguration(), DefaultDriverConfigLoader.DEFAULT_ROOT_PATH);
126140
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
127141
return builder.build();
128142
}
129143

130-
private Config cassandraConfiguration(CassandraProperties properties) {
144+
private Config cassandraConfiguration() {
131145
ConfigFactory.invalidateCaches();
132146
Config config = ConfigFactory.defaultOverrides();
133-
config = config.withFallback(mapConfig(properties));
134-
if (properties.getConfig() != null) {
135-
config = config.withFallback(loadConfig(properties.getConfig()));
147+
config = config.withFallback(mapConfig());
148+
if (this.properties.getConfig() != null) {
149+
config = config.withFallback(loadConfig(this.properties.getConfig()));
136150
}
137151
config = config.withFallback(SPRING_BOOT_DEFAULTS);
138152
config = config.withFallback(ConfigFactory.defaultReference());
@@ -148,32 +162,32 @@ private Config loadConfig(Resource resource) {
148162
}
149163
}
150164

151-
private Config mapConfig(CassandraProperties properties) {
165+
private Config mapConfig() {
152166
CassandraDriverOptions options = new CassandraDriverOptions();
153167
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
154-
map.from(properties.getSessionName())
168+
map.from(this.properties.getSessionName())
155169
.whenHasText()
156170
.to((sessionName) -> options.add(DefaultDriverOption.SESSION_NAME, sessionName));
157-
map.from(properties::getUsername)
158-
.to((username) -> options.add(DefaultDriverOption.AUTH_PROVIDER_USER_NAME, username)
159-
.add(DefaultDriverOption.AUTH_PROVIDER_PASSWORD, properties.getPassword()));
160-
map.from(properties::getCompression)
171+
map.from(this.connectionDetails.getUsername())
172+
.to((value) -> options.add(DefaultDriverOption.AUTH_PROVIDER_USER_NAME, value)
173+
.add(DefaultDriverOption.AUTH_PROVIDER_PASSWORD, this.connectionDetails.getPassword()));
174+
map.from(this.properties::getCompression)
161175
.to((compression) -> options.add(DefaultDriverOption.PROTOCOL_COMPRESSION, compression));
162-
mapConnectionOptions(properties, options);
163-
mapPoolingOptions(properties, options);
164-
mapRequestOptions(properties, options);
165-
mapControlConnectionOptions(properties, options);
166-
map.from(mapContactPoints(properties))
176+
mapConnectionOptions(options);
177+
mapPoolingOptions(options);
178+
mapRequestOptions(options);
179+
mapControlConnectionOptions(options);
180+
map.from(mapContactPoints())
167181
.to((contactPoints) -> options.add(DefaultDriverOption.CONTACT_POINTS, contactPoints));
168-
map.from(properties.getLocalDatacenter())
182+
map.from(this.connectionDetails.getLocalDatacenter())
169183
.whenHasText()
170184
.to((localDatacenter) -> options.add(DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, localDatacenter));
171185
return options.build();
172186
}
173187

174-
private void mapConnectionOptions(CassandraProperties properties, CassandraDriverOptions options) {
188+
private void mapConnectionOptions(CassandraDriverOptions options) {
175189
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
176-
Connection connectionProperties = properties.getConnection();
190+
Connection connectionProperties = this.properties.getConnection();
177191
map.from(connectionProperties::getConnectTimeout)
178192
.asInt(Duration::toMillis)
179193
.to((connectTimeout) -> options.add(DefaultDriverOption.CONNECTION_CONNECT_TIMEOUT, connectTimeout));
@@ -182,9 +196,9 @@ private void mapConnectionOptions(CassandraProperties properties, CassandraDrive
182196
.to((initQueryTimeout) -> options.add(DefaultDriverOption.CONNECTION_INIT_QUERY_TIMEOUT, initQueryTimeout));
183197
}
184198

185-
private void mapPoolingOptions(CassandraProperties properties, CassandraDriverOptions options) {
199+
private void mapPoolingOptions(CassandraDriverOptions options) {
186200
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
187-
CassandraProperties.Pool poolProperties = properties.getPool();
201+
CassandraProperties.Pool poolProperties = this.properties.getPool();
188202
map.from(poolProperties::getIdleTimeout)
189203
.asInt(Duration::toMillis)
190204
.to((idleTimeout) -> options.add(DefaultDriverOption.HEARTBEAT_TIMEOUT, idleTimeout));
@@ -193,9 +207,9 @@ private void mapPoolingOptions(CassandraProperties properties, CassandraDriverOp
193207
.to((heartBeatInterval) -> options.add(DefaultDriverOption.HEARTBEAT_INTERVAL, heartBeatInterval));
194208
}
195209

196-
private void mapRequestOptions(CassandraProperties properties, CassandraDriverOptions options) {
210+
private void mapRequestOptions(CassandraDriverOptions options) {
197211
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
198-
Request requestProperties = properties.getRequest();
212+
Request requestProperties = this.properties.getRequest();
199213
map.from(requestProperties::getTimeout)
200214
.asInt(Duration::toMillis)
201215
.to(((timeout) -> options.add(DefaultDriverOption.REQUEST_TIMEOUT, timeout)));
@@ -222,40 +236,19 @@ private void mapRequestOptions(CassandraProperties properties, CassandraDriverOp
222236
.to((drainInterval) -> options.add(DefaultDriverOption.REQUEST_THROTTLER_DRAIN_INTERVAL, drainInterval));
223237
}
224238

225-
private void mapControlConnectionOptions(CassandraProperties properties, CassandraDriverOptions options) {
239+
private void mapControlConnectionOptions(CassandraDriverOptions options) {
226240
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
227-
Controlconnection controlProperties = properties.getControlconnection();
241+
Controlconnection controlProperties = this.properties.getControlconnection();
228242
map.from(controlProperties::getTimeout)
229243
.asInt(Duration::toMillis)
230244
.to((timeout) -> options.add(DefaultDriverOption.CONTROL_CONNECTION_TIMEOUT, timeout));
231245
}
232246

233-
private List<String> mapContactPoints(CassandraProperties properties) {
234-
if (properties.getContactPoints() != null) {
235-
return properties.getContactPoints()
236-
.stream()
237-
.map((candidate) -> formatContactPoint(candidate, properties.getPort()))
238-
.toList();
239-
}
240-
return null;
241-
}
242-
243-
private String formatContactPoint(String candidate, int port) {
244-
int i = candidate.lastIndexOf(':');
245-
if (i == -1 || !isPort(() -> candidate.substring(i + 1))) {
246-
return String.format("%s:%s", candidate, port);
247-
}
248-
return candidate;
249-
}
250-
251-
private boolean isPort(Supplier<String> value) {
252-
try {
253-
int i = Integer.parseInt(value.get());
254-
return i > 0 && i < 65535;
255-
}
256-
catch (Exception ex) {
257-
return false;
258-
}
247+
private List<String> mapContactPoints() {
248+
return this.connectionDetails.getContactPoints()
249+
.stream()
250+
.map((node) -> node.host() + ":" + node.port())
251+
.toList();
259252
}
260253

261254
private static class CassandraDriverOptions {
@@ -293,4 +286,61 @@ private static String createKeyFor(DriverOption option) {
293286

294287
}
295288

289+
/**
290+
* Adapts {@link CassandraProperties} to {@link CassandraConnectionDetails}.
291+
*/
292+
private static final class PropertiesCassandraConnectionDetails implements CassandraConnectionDetails {
293+
294+
private final CassandraProperties properties;
295+
296+
private PropertiesCassandraConnectionDetails(CassandraProperties properties) {
297+
this.properties = properties;
298+
}
299+
300+
@Override
301+
public List<Node> getContactPoints() {
302+
List<String> contactPoints = this.properties.getContactPoints();
303+
return (contactPoints != null) ? contactPoints.stream().map(this::asNode).toList()
304+
: Collections.emptyList();
305+
}
306+
307+
@Override
308+
public String getUsername() {
309+
return this.properties.getUsername();
310+
}
311+
312+
@Override
313+
public String getPassword() {
314+
return this.properties.getPassword();
315+
}
316+
317+
@Override
318+
public String getLocalDatacenter() {
319+
return this.properties.getLocalDatacenter();
320+
}
321+
322+
private Node asNode(String contactPoint) {
323+
int i = contactPoint.lastIndexOf(':');
324+
if (i >= 0) {
325+
String portCandidate = contactPoint.substring(i + 1);
326+
Integer port = asPort(portCandidate);
327+
if (port != null) {
328+
return new Node(contactPoint.substring(0, i), port);
329+
}
330+
}
331+
return new Node(contactPoint, this.properties.getPort());
332+
}
333+
334+
private Integer asPort(String value) {
335+
try {
336+
int i = Integer.parseInt(value);
337+
return (i > 0 && i < 65535) ? i : null;
338+
}
339+
catch (Exception ex) {
340+
return null;
341+
}
342+
}
343+
344+
}
345+
296346
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2012-2023 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.cassandra;
18+
19+
import java.util.List;
20+
21+
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
22+
23+
/**
24+
* Details required to establish a connection to a Cassandra service.
25+
*
26+
* @author Moritz Halbritter
27+
* @author Andy Wilkinson
28+
* @author Phillip Webb
29+
* @since 3.1.0
30+
*/
31+
public interface CassandraConnectionDetails extends ConnectionDetails {
32+
33+
/**
34+
* Cluster node addresses.
35+
* @return the cluster node addresses
36+
*/
37+
List<Node> getContactPoints();
38+
39+
/**
40+
* Login user of the server.
41+
* @return the login user of the server or {@code null}
42+
*/
43+
default String getUsername() {
44+
return null;
45+
}
46+
47+
/**
48+
* Login password of the server.
49+
* @return the login password of the server or {@code null}
50+
*/
51+
default String getPassword() {
52+
return null;
53+
}
54+
55+
/**
56+
* Datacenter that is considered "local". Contact points should be from this
57+
* datacenter.
58+
* @return the datacenter that is considered "local"
59+
*/
60+
String getLocalDatacenter();
61+
62+
/**
63+
* A Cassandra node.
64+
*
65+
* @param host the hostname
66+
* @param port the port
67+
*/
68+
record Node(String host, int port) {
69+
}
70+
71+
}

0 commit comments

Comments
 (0)