Skip to content

Commit 9f187bb

Browse files
wilkinsonamhalbritterphilwebb
committed
Add ConnectionDetail support to Couchbase auto-configuration
Update Couchbase auto-configuration so that `CouchbaseConnectionDetails` 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 4307fdc commit 9f187bb

File tree

3 files changed

+182
-11
lines changed

3 files changed

+182
-11
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java

Lines changed: 81 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,18 @@
3434
import org.springframework.beans.factory.ObjectProvider;
3535
import org.springframework.boot.autoconfigure.AutoConfiguration;
3636
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
37+
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
38+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
3739
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3840
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3941
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
4042
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
43+
import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.CouchbaseCondition;
4144
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Timeouts;
4245
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
4346
import org.springframework.boot.context.properties.EnableConfigurationProperties;
4447
import org.springframework.context.annotation.Bean;
48+
import org.springframework.context.annotation.Conditional;
4549
import org.springframework.context.annotation.Configuration;
4650
import org.springframework.core.Ordered;
4751
import org.springframework.util.ResourceUtils;
@@ -52,34 +56,49 @@
5256
* @author Eddú Meléndez
5357
* @author Stephane Nicoll
5458
* @author Yulin Qin
59+
* @author Moritz Halbritter
60+
* @author Andy Wilkinson
61+
* @author Phillip Webb
5562
* @since 1.4.0
5663
*/
5764
@AutoConfiguration(after = JacksonAutoConfiguration.class)
5865
@ConditionalOnClass(Cluster.class)
59-
@ConditionalOnProperty("spring.couchbase.connection-string")
66+
@Conditional(CouchbaseCondition.class)
6067
@EnableConfigurationProperties(CouchbaseProperties.class)
6168
public class CouchbaseAutoConfiguration {
6269

70+
private final CouchbaseProperties properties;
71+
72+
private final CouchbaseConnectionDetails connectionDetails;
73+
74+
CouchbaseAutoConfiguration(CouchbaseProperties properties,
75+
ObjectProvider<CouchbaseConnectionDetails> connectionDetails) {
76+
this.properties = properties;
77+
this.connectionDetails = connectionDetails
78+
.getIfAvailable(() -> new PropertiesCouchbaseConnectionDetails(properties));
79+
}
80+
6381
@Bean
6482
@ConditionalOnMissingBean
65-
public ClusterEnvironment couchbaseClusterEnvironment(CouchbaseProperties properties,
83+
public ClusterEnvironment couchbaseClusterEnvironment(
6684
ObjectProvider<ClusterEnvironmentBuilderCustomizer> customizers) {
67-
Builder builder = initializeEnvironmentBuilder(properties);
85+
Builder builder = initializeEnvironmentBuilder();
6886
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
6987
return builder.build();
7088
}
7189

7290
@Bean(destroyMethod = "disconnect")
7391
@ConditionalOnMissingBean
74-
public Cluster couchbaseCluster(CouchbaseProperties properties, ClusterEnvironment couchbaseClusterEnvironment) {
75-
ClusterOptions options = ClusterOptions.clusterOptions(properties.getUsername(), properties.getPassword())
92+
public Cluster couchbaseCluster(ClusterEnvironment couchbaseClusterEnvironment) {
93+
ClusterOptions options = ClusterOptions
94+
.clusterOptions(this.connectionDetails.getUsername(), this.connectionDetails.getPassword())
7695
.environment(couchbaseClusterEnvironment);
77-
return Cluster.connect(properties.getConnectionString(), options);
96+
return Cluster.connect(this.connectionDetails.getConnectionString(), options);
7897
}
7998

80-
private ClusterEnvironment.Builder initializeEnvironmentBuilder(CouchbaseProperties properties) {
99+
private ClusterEnvironment.Builder initializeEnvironmentBuilder() {
81100
ClusterEnvironment.Builder builder = ClusterEnvironment.builder();
82-
Timeouts timeouts = properties.getEnv().getTimeouts();
101+
Timeouts timeouts = this.properties.getEnv().getTimeouts();
83102
builder.timeoutConfig((config) -> config.kvTimeout(timeouts.getKeyValue())
84103
.analyticsTimeout(timeouts.getAnalytics())
85104
.kvDurableTimeout(timeouts.getKeyValueDurable())
@@ -89,13 +108,14 @@ private ClusterEnvironment.Builder initializeEnvironmentBuilder(CouchbasePropert
89108
.managementTimeout(timeouts.getManagement())
90109
.connectTimeout(timeouts.getConnect())
91110
.disconnectTimeout(timeouts.getDisconnect()));
92-
CouchbaseProperties.Io io = properties.getEnv().getIo();
111+
CouchbaseProperties.Io io = this.properties.getEnv().getIo();
93112
builder.ioConfig((config) -> config.maxHttpConnections(io.getMaxEndpoints())
94113
.numKvConnections(io.getMinEndpoints())
95114
.idleHttpConnectionTimeout(io.getIdleHttpConnectionTimeout()));
96-
if (properties.getEnv().getSsl().getEnabled()) {
115+
if ((this.connectionDetails instanceof PropertiesCouchbaseConnectionDetails)
116+
&& this.properties.getEnv().getSsl().getEnabled()) {
97117
builder.securityConfig((config) -> config.enableTls(true)
98-
.trustManagerFactory(getTrustManagerFactory(properties.getEnv().getSsl())));
118+
.trustManagerFactory(getTrustManagerFactory(this.properties.getEnv().getSsl())));
99119
}
100120
return builder;
101121
}
@@ -157,4 +177,54 @@ public int getOrder() {
157177

158178
}
159179

180+
/**
181+
* Condition that matches when {@code spring.couchbase.connection-string} has been
182+
* configured or there is a {@link CouchbaseConnectionDetails} bean.
183+
*/
184+
static final class CouchbaseCondition extends AnyNestedCondition {
185+
186+
CouchbaseCondition() {
187+
super(ConfigurationPhase.REGISTER_BEAN);
188+
}
189+
190+
@ConditionalOnProperty(prefix = "spring.couchbase", name = "connection-string")
191+
private static final class CouchbaseUrlCondition {
192+
193+
}
194+
195+
@ConditionalOnBean(CouchbaseConnectionDetails.class)
196+
private static final class CouchbaseConnectionDetailsCondition {
197+
198+
}
199+
200+
}
201+
202+
/**
203+
* Adapts {@link CouchbaseProperties} to {@link CouchbaseConnectionDetails}.
204+
*/
205+
static final class PropertiesCouchbaseConnectionDetails implements CouchbaseConnectionDetails {
206+
207+
private final CouchbaseProperties properties;
208+
209+
PropertiesCouchbaseConnectionDetails(CouchbaseProperties properties) {
210+
this.properties = properties;
211+
}
212+
213+
@Override
214+
public String getConnectionString() {
215+
return this.properties.getConnectionString();
216+
}
217+
218+
@Override
219+
public String getUsername() {
220+
return this.properties.getUsername();
221+
}
222+
223+
@Override
224+
public String getPassword() {
225+
return this.properties.getPassword();
226+
}
227+
228+
}
229+
160230
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.couchbase;
18+
19+
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
20+
21+
/**
22+
* Details required to establish a connection to a Couchbase service.
23+
*
24+
* @author Moritz Halbritter
25+
* @author Andy Wilkinson
26+
* @author Phillip Webb
27+
* @since 3.1.0
28+
*/
29+
public interface CouchbaseConnectionDetails extends ConnectionDetails {
30+
31+
/**
32+
* Connection string used to locate the Couchbase cluster.
33+
* @return the connection string used to locate the Couchbase cluster
34+
*/
35+
String getConnectionString();
36+
37+
/**
38+
* Cluster username.
39+
* @return the cluster username
40+
*/
41+
String getUsername();
42+
43+
/**
44+
* Cluster password.
45+
* @return the cluster password
46+
*/
47+
String getPassword();
48+
49+
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
*
4949
* @author Eddú Meléndez
5050
* @author Stephane Nicoll
51+
* @author Moritz Halbritter
52+
* @author Andy Wilkinson
53+
* @author Phillip Webb
5154
*/
5255
class CouchbaseAutoConfigurationTests {
5356

@@ -60,6 +63,19 @@ void connectionStringIsRequired() {
6063
.doesNotHaveBean(Cluster.class));
6164
}
6265

66+
@Test
67+
void shouldUseConnectionDetails() {
68+
this.contextRunner.withBean(CouchbaseConnectionDetails.class, this::couchbaseConnectionDetails)
69+
.run((context) -> {
70+
assertThat(context).hasSingleBean(ClusterEnvironment.class).hasSingleBean(Cluster.class);
71+
Cluster cluster = context.getBean(Cluster.class);
72+
assertThat(cluster.core()).extracting("connectionString.hosts")
73+
.asList()
74+
.extractingResultOf("host")
75+
.containsExactly("couchbase.example.com");
76+
});
77+
}
78+
6379
@Test
6480
void connectionStringCreateEnvironmentAndCluster() {
6581
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
@@ -71,6 +87,21 @@ void connectionStringCreateEnvironmentAndCluster() {
7187
});
7288
}
7389

90+
@Test
91+
void connectionDetailsShouldOverrideProperties() {
92+
this.contextRunner.withBean(CouchbaseConnectionDetails.class, this::couchbaseConnectionDetails)
93+
.withPropertyValues("spring.couchbase.connection-string=localhost", "spring.couchbase.username=a-user",
94+
"spring.couchbase.password=a-password")
95+
.run((context) -> {
96+
assertThat(context).hasSingleBean(ClusterEnvironment.class).hasSingleBean(Cluster.class);
97+
Cluster cluster = context.getBean(Cluster.class);
98+
assertThat(cluster.core()).extracting("connectionString.hosts")
99+
.asList()
100+
.extractingResultOf("host")
101+
.containsExactly("couchbase.example.com");
102+
});
103+
}
104+
74105
@Test
75106
void whenObjectMapperBeanIsDefinedThenClusterEnvironmentObjectMapperIsDerivedFromIt() {
76107
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
@@ -176,6 +207,27 @@ void customizeEnvWithCustomCouchbaseConfiguration() {
176207
});
177208
}
178209

210+
private CouchbaseConnectionDetails couchbaseConnectionDetails() {
211+
return new CouchbaseConnectionDetails() {
212+
213+
@Override
214+
public String getConnectionString() {
215+
return "couchbase.example.com";
216+
}
217+
218+
@Override
219+
public String getUsername() {
220+
return "user-1";
221+
}
222+
223+
@Override
224+
public String getPassword() {
225+
return "password-1";
226+
}
227+
228+
};
229+
}
230+
179231
@Configuration(proxyBeanMethods = false)
180232
static class ClusterEnvironmentCustomizerConfiguration {
181233

0 commit comments

Comments
 (0)