Browse Source

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 <mkammerer@vmware.com>
Co-Authored-By: Phillip Webb <pwebb@vmware.com>
pull/34759/head
Andy Wilkinson 3 years ago
parent
commit
9f187bb13a
  1. 92
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java
  2. 49
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseConnectionDetails.java
  3. 52
      spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java

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

@ -34,14 +34,18 @@ import com.fasterxml.jackson.databind.ObjectMapper; @@ -34,14 +34,18 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.CouchbaseCondition;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Timeouts;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.util.ResourceUtils;
@ -52,34 +56,49 @@ import org.springframework.util.ResourceUtils; @@ -52,34 +56,49 @@ import org.springframework.util.ResourceUtils;
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Yulin Qin
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 1.4.0
*/
@AutoConfiguration(after = JacksonAutoConfiguration.class)
@ConditionalOnClass(Cluster.class)
@ConditionalOnProperty("spring.couchbase.connection-string")
@Conditional(CouchbaseCondition.class)
@EnableConfigurationProperties(CouchbaseProperties.class)
public class CouchbaseAutoConfiguration {
private final CouchbaseProperties properties;
private final CouchbaseConnectionDetails connectionDetails;
CouchbaseAutoConfiguration(CouchbaseProperties properties,
ObjectProvider<CouchbaseConnectionDetails> connectionDetails) {
this.properties = properties;
this.connectionDetails = connectionDetails
.getIfAvailable(() -> new PropertiesCouchbaseConnectionDetails(properties));
}
@Bean
@ConditionalOnMissingBean
public ClusterEnvironment couchbaseClusterEnvironment(CouchbaseProperties properties,
public ClusterEnvironment couchbaseClusterEnvironment(
ObjectProvider<ClusterEnvironmentBuilderCustomizer> customizers) {
Builder builder = initializeEnvironmentBuilder(properties);
Builder builder = initializeEnvironmentBuilder();
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
@Bean(destroyMethod = "disconnect")
@ConditionalOnMissingBean
public Cluster couchbaseCluster(CouchbaseProperties properties, ClusterEnvironment couchbaseClusterEnvironment) {
ClusterOptions options = ClusterOptions.clusterOptions(properties.getUsername(), properties.getPassword())
public Cluster couchbaseCluster(ClusterEnvironment couchbaseClusterEnvironment) {
ClusterOptions options = ClusterOptions
.clusterOptions(this.connectionDetails.getUsername(), this.connectionDetails.getPassword())
.environment(couchbaseClusterEnvironment);
return Cluster.connect(properties.getConnectionString(), options);
return Cluster.connect(this.connectionDetails.getConnectionString(), options);
}
private ClusterEnvironment.Builder initializeEnvironmentBuilder(CouchbaseProperties properties) {
private ClusterEnvironment.Builder initializeEnvironmentBuilder() {
ClusterEnvironment.Builder builder = ClusterEnvironment.builder();
Timeouts timeouts = properties.getEnv().getTimeouts();
Timeouts timeouts = this.properties.getEnv().getTimeouts();
builder.timeoutConfig((config) -> config.kvTimeout(timeouts.getKeyValue())
.analyticsTimeout(timeouts.getAnalytics())
.kvDurableTimeout(timeouts.getKeyValueDurable())
@ -89,13 +108,14 @@ public class CouchbaseAutoConfiguration { @@ -89,13 +108,14 @@ public class CouchbaseAutoConfiguration {
.managementTimeout(timeouts.getManagement())
.connectTimeout(timeouts.getConnect())
.disconnectTimeout(timeouts.getDisconnect()));
CouchbaseProperties.Io io = properties.getEnv().getIo();
CouchbaseProperties.Io io = this.properties.getEnv().getIo();
builder.ioConfig((config) -> config.maxHttpConnections(io.getMaxEndpoints())
.numKvConnections(io.getMinEndpoints())
.idleHttpConnectionTimeout(io.getIdleHttpConnectionTimeout()));
if (properties.getEnv().getSsl().getEnabled()) {
if ((this.connectionDetails instanceof PropertiesCouchbaseConnectionDetails)
&& this.properties.getEnv().getSsl().getEnabled()) {
builder.securityConfig((config) -> config.enableTls(true)
.trustManagerFactory(getTrustManagerFactory(properties.getEnv().getSsl())));
.trustManagerFactory(getTrustManagerFactory(this.properties.getEnv().getSsl())));
}
return builder;
}
@ -157,4 +177,54 @@ public class CouchbaseAutoConfiguration { @@ -157,4 +177,54 @@ public class CouchbaseAutoConfiguration {
}
/**
* Condition that matches when {@code spring.couchbase.connection-string} has been
* configured or there is a {@link CouchbaseConnectionDetails} bean.
*/
static final class CouchbaseCondition extends AnyNestedCondition {
CouchbaseCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty(prefix = "spring.couchbase", name = "connection-string")
private static final class CouchbaseUrlCondition {
}
@ConditionalOnBean(CouchbaseConnectionDetails.class)
private static final class CouchbaseConnectionDetailsCondition {
}
}
/**
* Adapts {@link CouchbaseProperties} to {@link CouchbaseConnectionDetails}.
*/
static final class PropertiesCouchbaseConnectionDetails implements CouchbaseConnectionDetails {
private final CouchbaseProperties properties;
PropertiesCouchbaseConnectionDetails(CouchbaseProperties properties) {
this.properties = properties;
}
@Override
public String getConnectionString() {
return this.properties.getConnectionString();
}
@Override
public String getUsername() {
return this.properties.getUsername();
}
@Override
public String getPassword() {
return this.properties.getPassword();
}
}
}

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

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
/*
* Copyright 2012-2023 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.couchbase;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to a Couchbase service.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @since 3.1.0
*/
public interface CouchbaseConnectionDetails extends ConnectionDetails {
/**
* Connection string used to locate the Couchbase cluster.
* @return the connection string used to locate the Couchbase cluster
*/
String getConnectionString();
/**
* Cluster username.
* @return the cluster username
*/
String getUsername();
/**
* Cluster password.
* @return the cluster password
*/
String getPassword();
}

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

@ -48,6 +48,9 @@ import static org.mockito.Mockito.mock; @@ -48,6 +48,9 @@ import static org.mockito.Mockito.mock;
*
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class CouchbaseAutoConfigurationTests {
@ -60,6 +63,19 @@ class CouchbaseAutoConfigurationTests { @@ -60,6 +63,19 @@ class CouchbaseAutoConfigurationTests {
.doesNotHaveBean(Cluster.class));
}
@Test
void shouldUseConnectionDetails() {
this.contextRunner.withBean(CouchbaseConnectionDetails.class, this::couchbaseConnectionDetails)
.run((context) -> {
assertThat(context).hasSingleBean(ClusterEnvironment.class).hasSingleBean(Cluster.class);
Cluster cluster = context.getBean(Cluster.class);
assertThat(cluster.core()).extracting("connectionString.hosts")
.asList()
.extractingResultOf("host")
.containsExactly("couchbase.example.com");
});
}
@Test
void connectionStringCreateEnvironmentAndCluster() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
@ -71,6 +87,21 @@ class CouchbaseAutoConfigurationTests { @@ -71,6 +87,21 @@ class CouchbaseAutoConfigurationTests {
});
}
@Test
void connectionDetailsShouldOverrideProperties() {
this.contextRunner.withBean(CouchbaseConnectionDetails.class, this::couchbaseConnectionDetails)
.withPropertyValues("spring.couchbase.connection-string=localhost", "spring.couchbase.username=a-user",
"spring.couchbase.password=a-password")
.run((context) -> {
assertThat(context).hasSingleBean(ClusterEnvironment.class).hasSingleBean(Cluster.class);
Cluster cluster = context.getBean(Cluster.class);
assertThat(cluster.core()).extracting("connectionString.hosts")
.asList()
.extractingResultOf("host")
.containsExactly("couchbase.example.com");
});
}
@Test
void whenObjectMapperBeanIsDefinedThenClusterEnvironmentObjectMapperIsDerivedFromIt() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
@ -176,6 +207,27 @@ class CouchbaseAutoConfigurationTests { @@ -176,6 +207,27 @@ class CouchbaseAutoConfigurationTests {
});
}
private CouchbaseConnectionDetails couchbaseConnectionDetails() {
return new CouchbaseConnectionDetails() {
@Override
public String getConnectionString() {
return "couchbase.example.com";
}
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
};
}
@Configuration(proxyBeanMethods = false)
static class ClusterEnvironmentCustomizerConfiguration {

Loading…
Cancel
Save