Upgrade to Couchbase SDK v3

This commit upgrades to the Couchbase SDK v3 which brings the following
breaking changes:

* Bootstrap hosts have been replaced by a connection string and the
authentication is now mandatory.
* A `Bucket` is no longer auto-configured. The
`spring.couchbase.bucket.*` properties have been removed
* `ClusterInfo` no longer exists and has been replaced by a dedicated
API on `Cluster`.
* `CouchbaseEnvironment` no longer exist in favour of
`ClusterEnvironment`, the customizer has been renamed accordingly.
* The bootstrap-related properties have been removed. Users requiring
custom ports should supply the seed nodes and initialize a Cluster
themselves.
* The endpoints-related configuration has been consolidated in a
single IO configuration.

The Spring Data Couchbase provides an integration with the new SDK. This
leads to the following changes:

* A convenient `CouchbaseClientFactory` is auto-configured.
* Repositories are configured against a bucket and a scope. Those can
be set via configuration in `spring.data.couchbase.*`.
* The default consistency property has been removed in favour of a more
flexible annotation on the repository query methods instead. You can now
specify different query consistency on a per method basis.
* The `CacheManager` implementation is provided, as do other stores for
consistency so a dependency on `couchbase-spring-cache` is no longer
required.

See gh-19893

Co-authored-by: Michael Nitschinger <michael@nitschinger.at>
This commit is contained in:
Stephane Nicoll 2020-03-17 14:58:22 +01:00
parent e3899df22c
commit abe43b2e83
42 changed files with 1112 additions and 708 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -16,13 +16,14 @@
package org.springframework.boot.actuate.couchbase;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import com.couchbase.client.core.message.internal.DiagnosticsReport;
import com.couchbase.client.core.message.internal.EndpointHealth;
import com.couchbase.client.core.state.LifecycleState;
import com.couchbase.client.core.diagnostics.ClusterState;
import com.couchbase.client.core.diagnostics.DiagnosticsResult;
import com.couchbase.client.core.diagnostics.EndpointDiagnostics;
import org.springframework.boot.actuate.health.Health.Builder;
@ -33,35 +34,29 @@ import org.springframework.boot.actuate.health.Health.Builder;
*/
class CouchbaseHealth {
private final DiagnosticsReport diagnostics;
private final DiagnosticsResult diagnostics;
CouchbaseHealth(DiagnosticsReport diagnostics) {
CouchbaseHealth(DiagnosticsResult diagnostics) {
this.diagnostics = diagnostics;
}
void applyTo(Builder builder) {
builder = isCouchbaseUp(this.diagnostics) ? builder.up() : builder.down();
builder.withDetail("sdk", this.diagnostics.sdk());
builder.withDetail("endpoints",
this.diagnostics.endpoints().stream().map(this::describe).collect(Collectors.toList()));
builder.withDetail("endpoints", this.diagnostics.endpoints().values().stream().flatMap(Collection::stream)
.map(this::describe).collect(Collectors.toList()));
}
private boolean isCouchbaseUp(DiagnosticsReport diagnostics) {
for (EndpointHealth health : diagnostics.endpoints()) {
LifecycleState state = health.state();
if (state != LifecycleState.CONNECTED && state != LifecycleState.IDLE) {
return false;
}
}
return true;
private boolean isCouchbaseUp(DiagnosticsResult diagnostics) {
return diagnostics.state() == ClusterState.ONLINE;
}
private Map<String, Object> describe(EndpointHealth endpointHealth) {
private Map<String, Object> describe(EndpointDiagnostics endpointHealth) {
Map<String, Object> map = new HashMap<>();
map.put("id", endpointHealth.id());
map.put("lastActivity", endpointHealth.lastActivity());
map.put("local", endpointHealth.local().toString());
map.put("remote", endpointHealth.remote().toString());
map.put("local", endpointHealth.local());
map.put("remote", endpointHealth.remote());
map.put("state", endpointHealth.state());
map.put("type", endpointHealth.type());
return map;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -16,7 +16,7 @@
package org.springframework.boot.actuate.couchbase;
import com.couchbase.client.core.message.internal.DiagnosticsReport;
import com.couchbase.client.core.diagnostics.DiagnosticsResult;
import com.couchbase.client.java.Cluster;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
@ -48,7 +48,7 @@ public class CouchbaseHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
DiagnosticsReport diagnostics = this.cluster.diagnostics();
DiagnosticsResult diagnostics = this.cluster.diagnostics();
new CouchbaseHealth(diagnostics).applyTo(builder);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -15,7 +15,7 @@
*/
package org.springframework.boot.actuate.couchbase;
import com.couchbase.client.core.message.internal.DiagnosticsReport;
import com.couchbase.client.core.diagnostics.DiagnosticsResult;
import com.couchbase.client.java.Cluster;
import reactor.core.publisher.Mono;
@ -45,7 +45,7 @@ public class CouchbaseReactiveHealthIndicator extends AbstractReactiveHealthIndi
@Override
protected Mono<Health> doHealthCheck(Health.Builder builder) {
DiagnosticsReport diagnostics = this.cluster.diagnostics();
DiagnosticsResult diagnostics = this.cluster.diagnostics();
new CouchbaseHealth(diagnostics).applyTo(builder);
return Mono.just(builder.build());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -16,15 +16,16 @@
package org.springframework.boot.actuate.couchbase;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.couchbase.client.core.message.internal.DiagnosticsReport;
import com.couchbase.client.core.message.internal.EndpointHealth;
import com.couchbase.client.core.diagnostics.DiagnosticsResult;
import com.couchbase.client.core.diagnostics.EndpointDiagnostics;
import com.couchbase.client.core.endpoint.EndpointState;
import com.couchbase.client.core.service.ServiceType;
import com.couchbase.client.core.state.LifecycleState;
import com.couchbase.client.java.Cluster;
import org.junit.jupiter.api.Test;
@ -49,9 +50,11 @@ class CouchbaseHealthIndicatorTests {
void couchbaseClusterIsUp() {
Cluster cluster = mock(Cluster.class);
CouchbaseHealthIndicator healthIndicator = new CouchbaseHealthIndicator(cluster);
List<EndpointHealth> endpoints = Arrays.asList(new EndpointHealth(ServiceType.BINARY, LifecycleState.CONNECTED,
new InetSocketAddress(0), new InetSocketAddress(0), 1234, "endpoint-1"));
DiagnosticsReport diagnostics = new DiagnosticsReport(endpoints, "test-sdk", "test-id", null);
Map<ServiceType, List<EndpointDiagnostics>> endpoints = Collections.singletonMap(ServiceType.KV,
Collections.singletonList(new EndpointDiagnostics(ServiceType.KV, EndpointState.CONNECTED, "127.0.0.1",
"127.0.0.1", Optional.empty(), Optional.of(1234L), Optional.of("endpoint-1"))));
DiagnosticsResult diagnostics = new DiagnosticsResult(endpoints, "test-sdk", "test-id");
given(cluster.diagnostics()).willReturn(diagnostics);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
@ -66,12 +69,13 @@ class CouchbaseHealthIndicatorTests {
void couchbaseClusterIsDown() {
Cluster cluster = mock(Cluster.class);
CouchbaseHealthIndicator healthIndicator = new CouchbaseHealthIndicator(cluster);
List<EndpointHealth> endpoints = Arrays.asList(
new EndpointHealth(ServiceType.BINARY, LifecycleState.CONNECTED, new InetSocketAddress(0),
new InetSocketAddress(0), 1234, "endpoint-1"),
new EndpointHealth(ServiceType.BINARY, LifecycleState.CONNECTING, new InetSocketAddress(0),
new InetSocketAddress(0), 1234, "endpoint-2"));
DiagnosticsReport diagnostics = new DiagnosticsReport(endpoints, "test-sdk", "test-id", null);
Map<ServiceType, List<EndpointDiagnostics>> endpoints = Collections.singletonMap(ServiceType.KV,
Arrays.asList(
new EndpointDiagnostics(ServiceType.KV, EndpointState.CONNECTED, "127.0.0.1", "127.0.0.1",
Optional.empty(), Optional.of(1234L), Optional.of("endpoint-1")),
new EndpointDiagnostics(ServiceType.KV, EndpointState.CONNECTING, "127.0.0.1", "127.0.0.1",
Optional.empty(), Optional.of(1234L), Optional.of("endpoint-2"))));
DiagnosticsResult diagnostics = new DiagnosticsResult(endpoints, "test-sdk", "test-id");
given(cluster.diagnostics()).willReturn(diagnostics);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -15,16 +15,17 @@
*/
package org.springframework.boot.actuate.couchbase;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.couchbase.client.core.message.internal.DiagnosticsReport;
import com.couchbase.client.core.message.internal.EndpointHealth;
import com.couchbase.client.core.diagnostics.DiagnosticsResult;
import com.couchbase.client.core.diagnostics.EndpointDiagnostics;
import com.couchbase.client.core.endpoint.EndpointState;
import com.couchbase.client.core.service.ServiceType;
import com.couchbase.client.core.state.LifecycleState;
import com.couchbase.client.java.Cluster;
import org.junit.jupiter.api.Test;
@ -46,9 +47,10 @@ class CouchbaseReactiveHealthIndicatorTests {
void couchbaseClusterIsUp() {
Cluster cluster = mock(Cluster.class);
CouchbaseReactiveHealthIndicator healthIndicator = new CouchbaseReactiveHealthIndicator(cluster);
List<EndpointHealth> endpoints = Arrays.asList(new EndpointHealth(ServiceType.BINARY, LifecycleState.CONNECTED,
new InetSocketAddress(0), new InetSocketAddress(0), 1234, "endpoint-1"));
DiagnosticsReport diagnostics = new DiagnosticsReport(endpoints, "test-sdk", "test-id", null);
Map<ServiceType, List<EndpointDiagnostics>> endpoints = Collections.singletonMap(ServiceType.KV,
Collections.singletonList(new EndpointDiagnostics(ServiceType.KV, EndpointState.CONNECTED, "127.0.0.1",
"127.0.0.1", Optional.empty(), Optional.of(1234L), Optional.of("endpoint-1"))));
DiagnosticsResult diagnostics = new DiagnosticsResult(endpoints, "test-sdk", "test-id");
given(cluster.diagnostics()).willReturn(diagnostics);
Health health = healthIndicator.health().block(Duration.ofSeconds(30));
assertThat(health.getStatus()).isEqualTo(Status.UP);
@ -63,12 +65,13 @@ class CouchbaseReactiveHealthIndicatorTests {
void couchbaseClusterIsDown() {
Cluster cluster = mock(Cluster.class);
CouchbaseReactiveHealthIndicator healthIndicator = new CouchbaseReactiveHealthIndicator(cluster);
List<EndpointHealth> endpoints = Arrays.asList(
new EndpointHealth(ServiceType.BINARY, LifecycleState.CONNECTED, new InetSocketAddress(0),
new InetSocketAddress(0), 1234, "endpoint-1"),
new EndpointHealth(ServiceType.BINARY, LifecycleState.CONNECTING, new InetSocketAddress(0),
new InetSocketAddress(0), 1234, "endpoint-2"));
DiagnosticsReport diagnostics = new DiagnosticsReport(endpoints, "test-sdk", "test-id", null);
Map<ServiceType, List<EndpointDiagnostics>> endpoints = Collections.singletonMap(ServiceType.KV,
Arrays.asList(
new EndpointDiagnostics(ServiceType.KV, EndpointState.CONNECTED, "127.0.0.1", "127.0.0.1",
Optional.empty(), Optional.of(1234L), Optional.of("endpoint-1")),
new EndpointDiagnostics(ServiceType.KV, EndpointState.CONNECTING, "127.0.0.1", "127.0.0.1",
Optional.empty(), Optional.of(1234L), Optional.of("endpoint-2"))));
DiagnosticsResult diagnostics = new DiagnosticsResult(endpoints, "test-sdk", "test-id");
given(cluster.diagnostics()).willReturn(diagnostics);
Health health = healthIndicator.health().block(Duration.ofSeconds(30));
assertThat(health.getStatus()).isEqualTo(Status.DOWN);

View File

@ -17,7 +17,6 @@ dependencies {
optional(platform(project(":spring-boot-project:spring-boot-dependencies")))
optional("com.atomikos:transactions-jdbc")
optional("com.atomikos:transactions-jta")
optional("com.couchbase.client:couchbase-spring-cache")
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
@ -181,7 +180,6 @@ dependencies {
testImplementation("org.springframework.kafka:spring-kafka-test")
testImplementation("org.springframework.security:spring-security-test")
testImplementation("org.testcontainers:cassandra")
testImplementation("org.testcontainers:couchbase")
testImplementation("org.testcontainers:elasticsearch")
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:testcontainers")

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -16,23 +16,23 @@
package org.springframework.boot.autoconfigure.cache;
import java.time.Duration;
import java.util.LinkedHashSet;
import java.util.List;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.spring.cache.CacheBuilder;
import com.couchbase.client.spring.cache.CouchbaseCacheManager;
import com.couchbase.client.java.Cluster;
import org.springframework.boot.autoconfigure.cache.CacheProperties.Couchbase;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.cache.CouchbaseCacheManager;
import org.springframework.data.couchbase.cache.CouchbaseCacheManager.CouchbaseCacheManagerBuilder;
import org.springframework.util.ObjectUtils;
/**
* Couchbase cache configuration.
@ -41,22 +41,28 @@ import org.springframework.util.StringUtils;
* @since 1.4.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Bucket.class, CouchbaseCacheManager.class })
@ConditionalOnClass({ Cluster.class, CouchbaseClientFactory.class, CouchbaseCacheManager.class })
@ConditionalOnMissingBean(CacheManager.class)
@ConditionalOnSingleCandidate(Bucket.class)
@ConditionalOnSingleCandidate(CouchbaseClientFactory.class)
@Conditional(CacheCondition.class)
public class CouchbaseCacheConfiguration {
@Bean
public CouchbaseCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers customizers,
Bucket bucket) {
CouchbaseClientFactory clientFactory) {
List<String> cacheNames = cacheProperties.getCacheNames();
CacheBuilder builder = CacheBuilder.newInstance(bucket);
CouchbaseCacheManagerBuilder builder = CouchbaseCacheManager.builder(clientFactory);
Couchbase couchbase = cacheProperties.getCouchbase();
PropertyMapper.get().from(couchbase::getExpiration).whenNonNull().asInt(Duration::getSeconds)
.to(builder::withExpiration);
String[] names = StringUtils.toStringArray(cacheNames);
CouchbaseCacheManager cacheManager = new CouchbaseCacheManager(builder, names);
org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration config = org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration
.defaultCacheConfig();
if (couchbase.getExpiration() != null) {
config = config.entryExpiry(couchbase.getExpiration());
}
builder.cacheDefaults(config);
if (!ObjectUtils.isEmpty(cacheNames)) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
CouchbaseCacheManager cacheManager = builder.build();
return customizers.customize(cacheManager);
}

View File

@ -16,24 +16,23 @@
package org.springframework.boot.autoconfigure.couchbase;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import com.couchbase.client.java.env.ClusterEnvironment;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link CouchbaseEnvironment} via a {@link DefaultCouchbaseEnvironment.Builder} whilst
* retaining default auto-configuration.
* {@link ClusterEnvironment} via a {@link ClusterEnvironment.Builder} whilst retaining
* default auto-configuration.whilst retaining default auto-configuration.
*
* @author Stephane Nicoll
* @since 2.3.0
*/
@FunctionalInterface
public interface CouchbaseEnvironmentBuilderCustomizer {
public interface ClusterEnvironmentBuilderCustomizer {
/**
* Customize the {@link DefaultCouchbaseEnvironment.Builder}.
* Customize the {@link ClusterEnvironment.Builder}.
* @param builder the builder to customize
*/
void customize(DefaultCouchbaseEnvironment.Builder builder);
void customize(ClusterEnvironment.Builder builder);
}

View File

@ -16,28 +16,30 @@
package org.springframework.boot.autoconfigure.couchbase;
import com.couchbase.client.core.env.KeyValueServiceConfig;
import com.couchbase.client.core.env.QueryServiceConfig;
import com.couchbase.client.core.env.ViewServiceConfig;
import com.couchbase.client.java.Bucket;
import java.net.URL;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import com.couchbase.client.core.env.IoConfig;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.core.env.TimeoutConfig;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseBucket;
import com.couchbase.client.java.CouchbaseCluster;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment.Builder;
import com.couchbase.client.java.ClusterOptions;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.env.ClusterEnvironment.Builder;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Endpoints;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Timeouts;
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.context.annotation.DependsOn;
import org.springframework.util.ResourceUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Couchbase.
@ -48,106 +50,62 @@ import org.springframework.context.annotation.DependsOn;
* @since 1.4.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ CouchbaseBucket.class, Cluster.class })
@Conditional(OnBootstrapHostsCondition.class)
@ConditionalOnClass(Cluster.class)
@ConditionalOnProperty("spring.couchbase.connection-string")
@EnableConfigurationProperties(CouchbaseProperties.class)
public class CouchbaseAutoConfiguration {
@Bean
@ConditionalOnMissingBean(CouchbaseEnvironment.class)
public DefaultCouchbaseEnvironment couchbaseEnvironment(CouchbaseProperties properties,
ObjectProvider<CouchbaseEnvironmentBuilderCustomizer> customizers) {
@ConditionalOnMissingBean
public ClusterEnvironment couchbaseClusterEnvironment(CouchbaseProperties properties,
ObjectProvider<ClusterEnvironmentBuilderCustomizer> customizers) {
Builder builder = initializeEnvironmentBuilder(properties);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder.build();
}
@Bean
@ConditionalOnMissingBean(Cluster.class)
public CouchbaseCluster couchbaseCluster(CouchbaseProperties properties,
CouchbaseEnvironment couchbaseEnvironment) {
CouchbaseCluster couchbaseCluster = CouchbaseCluster.create(couchbaseEnvironment,
properties.getBootstrapHosts());
if (isRoleBasedAccessControlEnabled(properties)) {
return couchbaseCluster.authenticate(properties.getUsername(), properties.getPassword());
}
return couchbaseCluster;
}
@Bean
@Bean(destroyMethod = "disconnect")
@ConditionalOnMissingBean
@DependsOn("couchbaseClient")
public ClusterInfo couchbaseClusterInfo(CouchbaseProperties properties, Cluster couchbaseCluster) {
if (isRoleBasedAccessControlEnabled(properties)) {
return couchbaseCluster.clusterManager().info();
}
return couchbaseCluster.clusterManager(properties.getBucket().getName(), properties.getBucket().getPassword())
.info();
public Cluster couchbaseCluster(CouchbaseProperties properties, ClusterEnvironment couchbaseClusterEnvironment) {
ClusterOptions options = ClusterOptions.clusterOptions(properties.getUsername(), properties.getPassword())
.environment(couchbaseClusterEnvironment);
return Cluster.connect(properties.getConnectionString(), options);
}
@Bean
@ConditionalOnMissingBean
public Bucket couchbaseClient(CouchbaseProperties properties, Cluster couchbaseCluster) {
if (isRoleBasedAccessControlEnabled(properties)) {
return couchbaseCluster.openBucket(properties.getBucket().getName());
}
return couchbaseCluster.openBucket(properties.getBucket().getName(), properties.getBucket().getPassword());
}
private boolean isRoleBasedAccessControlEnabled(CouchbaseProperties properties) {
return properties.getUsername() != null && properties.getPassword() != null;
}
private DefaultCouchbaseEnvironment.Builder initializeEnvironmentBuilder(CouchbaseProperties properties) {
CouchbaseProperties.Endpoints endpoints = properties.getEnv().getEndpoints();
CouchbaseProperties.Timeouts timeouts = properties.getEnv().getTimeouts();
CouchbaseProperties.Bootstrap bootstrap = properties.getEnv().getBootstrap();
DefaultCouchbaseEnvironment.Builder builder = DefaultCouchbaseEnvironment.builder();
if (bootstrap.getHttpDirectPort() != null) {
builder.bootstrapHttpDirectPort(bootstrap.getHttpDirectPort());
}
if (bootstrap.getHttpSslPort() != null) {
builder.bootstrapHttpSslPort(bootstrap.getHttpSslPort());
}
if (timeouts.getConnect() != null) {
builder = builder.connectTimeout(timeouts.getConnect().toMillis());
}
builder = builder.keyValueServiceConfig(KeyValueServiceConfig.create(endpoints.getKeyValue()));
if (timeouts.getKeyValue() != null) {
builder = builder.kvTimeout(timeouts.getKeyValue().toMillis());
}
if (timeouts.getQuery() != null) {
builder = builder.queryTimeout(timeouts.getQuery().toMillis());
builder = builder.queryServiceConfig(getQueryServiceConfig(endpoints));
builder = builder.viewServiceConfig(getViewServiceConfig(endpoints));
}
if (timeouts.getSocketConnect() != null) {
builder = builder.socketConnectTimeout((int) timeouts.getSocketConnect().toMillis());
}
if (timeouts.getView() != null) {
builder = builder.viewTimeout(timeouts.getView().toMillis());
}
CouchbaseProperties.Ssl ssl = properties.getEnv().getSsl();
if (ssl.getEnabled()) {
builder = builder.sslEnabled(true);
if (ssl.getKeyStore() != null) {
builder = builder.sslKeystoreFile(ssl.getKeyStore());
}
if (ssl.getKeyStorePassword() != null) {
builder = builder.sslKeystorePassword(ssl.getKeyStorePassword());
}
private ClusterEnvironment.Builder initializeEnvironmentBuilder(CouchbaseProperties properties) {
ClusterEnvironment.Builder builder = ClusterEnvironment.builder();
Timeouts timeouts = properties.getEnv().getTimeouts();
builder.timeoutConfig(TimeoutConfig.kvTimeout(timeouts.getKeyValue()).queryTimeout(timeouts.getQuery())
.viewTimeout(timeouts.getView()).connectTimeout(timeouts.getConnect()));
CouchbaseProperties.Io io = properties.getEnv().getIo();
builder.ioConfig(IoConfig.maxHttpConnections(io.getMaxEndpoints()).numKvConnections(io.getMinEndpoints())
.idleHttpConnectionTimeout(io.getIdleHttpConnectionTimeout()));
if (properties.getEnv().getSsl().getEnabled()) {
builder.securityConfig(SecurityConfig.enableTls(true)
.trustManagerFactory(getTrustManagerFactory(properties.getEnv().getSsl())));
}
return builder;
}
private QueryServiceConfig getQueryServiceConfig(Endpoints endpoints) {
return QueryServiceConfig.create(endpoints.getQueryservice().getMinEndpoints(),
endpoints.getQueryservice().getMaxEndpoints());
private TrustManagerFactory getTrustManagerFactory(CouchbaseProperties.Ssl ssl) {
String resource = ssl.getKeyStore();
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = loadKeyStore(resource, ssl.getKeyStorePassword());
trustManagerFactory.init(keyStore);
return trustManagerFactory;
}
catch (Exception ex) {
throw new IllegalStateException("Could not load Couchbase key store '" + resource + "'", ex);
}
}
private ViewServiceConfig getViewServiceConfig(Endpoints endpoints) {
return ViewServiceConfig.create(endpoints.getViewservice().getMinEndpoints(),
endpoints.getViewservice().getMaxEndpoints());
private KeyStore loadKeyStore(String resource, String keyStorePassword) throws Exception {
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
URL url = ResourceUtils.getURL(resource);
store.load(url.openStream(), (keyStorePassword != null) ? keyStorePassword.toCharArray() : null);
return store;
}
}

View File

@ -17,7 +17,6 @@
package org.springframework.boot.autoconfigure.couchbase;
import java.time.Duration;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
@ -29,36 +28,35 @@ import org.springframework.util.StringUtils;
* @author Stephane Nicoll
* @author Yulin Qin
* @author Brian Clozel
* @author Michael Nitschinger
* @since 1.4.0
*/
@ConfigurationProperties(prefix = "spring.couchbase")
public class CouchbaseProperties {
/**
* Couchbase nodes (host or IP address) to bootstrap from.
* Connection string used to locate the Couchbase cluster.
*/
private List<String> bootstrapHosts;
private String connectionString;
/**
* Cluster username when using role based access.
* Cluster username.
*/
private String username;
/**
* Cluster password when using role based access.
* Cluster password.
*/
private String password;
private final Bucket bucket = new Bucket();
private final Env env = new Env();
public List<String> getBootstrapHosts() {
return this.bootstrapHosts;
public String getConnectionString() {
return this.connectionString;
}
public void setBootstrapHosts(List<String> bootstrapHosts) {
this.bootstrapHosts = bootstrapHosts;
public void setConnectionString(String connectionString) {
this.connectionString = connectionString;
}
public String getUsername() {
@ -77,60 +75,20 @@ public class CouchbaseProperties {
this.password = password;
}
public Bucket getBucket() {
return this.bucket;
}
public Env getEnv() {
return this.env;
}
public static class Bucket {
/**
* Name of the bucket to connect to.
*/
private String name = "default";
/**
* Password of the bucket.
*/
private String password = "";
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
}
public static class Env {
private final Bootstrap bootstrap = new Bootstrap();
private final Endpoints endpoints = new Endpoints();
private final Io io = new Io();
private final Ssl ssl = new Ssl();
private final Timeouts timeouts = new Timeouts();
public Bootstrap getBootstrap() {
return this.bootstrap;
}
public Endpoints getEndpoints() {
return this.endpoints;
public Io getIo() {
return this.io;
}
public Ssl getSsl() {
@ -143,67 +101,46 @@ public class CouchbaseProperties {
}
public static class Endpoints {
public static class Io {
/**
* Number of sockets per node against the key/value service.
* Minimum number of sockets per node.
*/
private int keyValue = 1;
private int minEndpoints = 1;
/**
* Query (N1QL) service configuration.
* Maximum number of sockets per node.
*/
private final CouchbaseService queryservice = new CouchbaseService();
private int maxEndpoints = 12;
/**
* View service configuration.
* Length of time an HTTP connection may remain idle before it is closed and
* removed from the pool.
*/
private final CouchbaseService viewservice = new CouchbaseService();
private Duration idleHttpConnectionTimeout = Duration.ofSeconds(30);
public int getKeyValue() {
return this.keyValue;
public int getMinEndpoints() {
return this.minEndpoints;
}
public void setKeyValue(int keyValue) {
this.keyValue = keyValue;
public void setMinEndpoints(int minEndpoints) {
this.minEndpoints = minEndpoints;
}
public CouchbaseService getQueryservice() {
return this.queryservice;
public int getMaxEndpoints() {
return this.maxEndpoints;
}
public CouchbaseService getViewservice() {
return this.viewservice;
public void setMaxEndpoints(int maxEndpoints) {
this.maxEndpoints = maxEndpoints;
}
public static class CouchbaseService {
/**
* Minimum number of sockets per node.
*/
private int minEndpoints = 1;
/**
* Maximum number of sockets per node.
*/
private int maxEndpoints = 1;
public int getMinEndpoints() {
return this.minEndpoints;
}
public void setMinEndpoints(int minEndpoints) {
this.minEndpoints = minEndpoints;
}
public int getMaxEndpoints() {
return this.maxEndpoints;
}
public void setMaxEndpoints(int maxEndpoints) {
this.maxEndpoints = maxEndpoints;
}
public Duration getIdleHttpConnectionTimeout() {
return this.idleHttpConnectionTimeout;
}
public void setIdleHttpConnectionTimeout(Duration idleHttpConnectionTimeout) {
this.idleHttpConnectionTimeout = idleHttpConnectionTimeout;
}
}
@ -255,29 +192,24 @@ public class CouchbaseProperties {
public static class Timeouts {
/**
* Bucket connections timeouts.
* Bucket connect timeouts.
*/
private Duration connect = Duration.ofMillis(5000);
private Duration connect = Duration.ofSeconds(10);
/**
* Blocking operations performed on a specific key timeout.
* operations performed on a specific key timeout.
*/
private Duration keyValue = Duration.ofMillis(2500);
/**
* N1QL query operations timeout.
*/
private Duration query = Duration.ofMillis(7500);
/**
* Socket connect connections timeout.
*/
private Duration socketConnect = Duration.ofMillis(1000);
private Duration query = Duration.ofSeconds(75);
/**
* Regular and geospatial view operations timeout.
*/
private Duration view = Duration.ofMillis(7500);
private Duration view = Duration.ofSeconds(75);
public Duration getConnect() {
return this.connect;
@ -303,14 +235,6 @@ public class CouchbaseProperties {
this.query = query;
}
public Duration getSocketConnect() {
return this.socketConnect;
}
public void setSocketConnect(Duration socketConnect) {
this.socketConnect = socketConnect;
}
public Duration getView() {
return this.view;
}
@ -321,34 +245,4 @@ public class CouchbaseProperties {
}
public static class Bootstrap {
/**
* Port for the HTTP bootstrap.
*/
private Integer httpDirectPort;
/**
* Port for the HTTPS bootstrap.
*/
private Integer httpSslPort;
public Integer getHttpDirectPort() {
return this.httpDirectPort;
}
public void setHttpDirectPort(Integer httpDirectPort) {
this.httpDirectPort = httpDirectPort;
}
public Integer getHttpSslPort() {
return this.httpSslPort;
}
public void setHttpSslPort(Integer httpSslPort) {
this.httpSslPort = httpSslPort;
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2012-2020 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.data.couchbase;
import com.couchbase.client.java.Cluster;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
/**
* Configuration for a {@link CouchbaseClientFactory}.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(Cluster.class)
@ConditionalOnProperty("spring.data.couchbase.bucket-name")
class CouchbaseClientFactoryConfiguration {
@Bean
@ConditionalOnMissingBean
CouchbaseClientFactory couchbaseClientFactory(Cluster cluster, CouchbaseDataProperties properties) {
return new SimpleCouchbaseClientFactory(cluster, properties.getBucketName(), properties.getScopeName());
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2012-2020 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.data.couchbase;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
/**
* Configuration for Couchbase-related beans that depend on a
* {@link CouchbaseClientFactory}.
*
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(CouchbaseClientFactory.class)
class CouchbaseClientFactoryDependentConfiguration {
@Bean(name = BeanNames.COUCHBASE_TEMPLATE)
@ConditionalOnMissingBean(name = BeanNames.COUCHBASE_TEMPLATE)
CouchbaseTemplate couchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
MappingCouchbaseConverter mappingCouchbaseConverter) {
return new CouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter);
}
@Bean(name = BeanNames.COUCHBASE_OPERATIONS_MAPPING)
@ConditionalOnMissingBean(name = BeanNames.COUCHBASE_OPERATIONS_MAPPING)
RepositoryOperationsMapping couchbaseRepositoryOperationsMapping(CouchbaseTemplate couchbaseTemplate) {
return new RepositoryOperationsMapping(couchbaseTemplate);
}
}

View File

@ -44,7 +44,8 @@ import org.springframework.data.couchbase.repository.CouchbaseRepository;
@ConditionalOnClass({ Bucket.class, CouchbaseRepository.class })
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, ValidationAutoConfiguration.class })
@EnableConfigurationProperties(CouchbaseDataProperties.class)
@Import(CouchbaseDataConfiguration.class)
@Import({ CouchbaseDataConfiguration.class, CouchbaseClientFactoryConfiguration.class,
CouchbaseClientFactoryDependentConfiguration.class })
public class CouchbaseDataAutoConfiguration {
@Configuration(proxyBeanMethods = false)

View File

@ -18,28 +18,20 @@ package org.springframework.boot.autoconfigure.data.couchbase;
import java.util.Collections;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.cluster.ClusterInfo;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.domain.EntityScanner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.mapping.model.FieldNamingStrategy;
/**
@ -50,8 +42,8 @@ import org.springframework.data.mapping.model.FieldNamingStrategy;
@Configuration(proxyBeanMethods = false)
class CouchbaseDataConfiguration {
@Bean(name = BeanNames.COUCHBASE_MAPPING_CONVERTER)
@ConditionalOnMissingBean(name = BeanNames.COUCHBASE_MAPPING_CONVERTER)
@Bean
@ConditionalOnMissingBean
MappingCouchbaseConverter couchbaseMappingConverter(CouchbaseDataProperties properties,
CouchbaseMappingContext couchbaseMappingContext, CouchbaseCustomConversions couchbaseCustomConversions) {
MappingCouchbaseConverter converter = new MappingCouchbaseConverter(couchbaseMappingContext,
@ -60,8 +52,8 @@ class CouchbaseDataConfiguration {
return converter;
}
@Bean(name = BeanNames.COUCHBASE_TRANSLATION_SERVICE)
@ConditionalOnMissingBean(name = BeanNames.COUCHBASE_TRANSLATION_SERVICE)
@Bean
@ConditionalOnMissingBean
TranslationService couchbaseTranslationService() {
return new JacksonTranslationService();
}
@ -80,6 +72,7 @@ class CouchbaseDataConfiguration {
mappingContext
.setFieldNamingStrategy((FieldNamingStrategy) BeanUtils.instantiateClass(fieldNamingStrategy));
}
mappingContext.setAutoIndexCreation(properties.isAutoIndex());
return mappingContext;
}
@ -89,31 +82,4 @@ class CouchbaseDataConfiguration {
return new CouchbaseCustomConversions(Collections.emptyList());
}
@Bean(name = BeanNames.COUCHBASE_INDEX_MANAGER)
@ConditionalOnMissingBean(name = BeanNames.COUCHBASE_INDEX_MANAGER)
IndexManager indexManager(CouchbaseDataProperties properties) {
if (properties.isAutoIndex()) {
return new IndexManager(true, true, true);
}
return new IndexManager(false, false, false);
}
@Bean(name = BeanNames.COUCHBASE_TEMPLATE)
@ConditionalOnMissingBean(name = BeanNames.COUCHBASE_TEMPLATE)
@ConditionalOnBean({ ClusterInfo.class, Bucket.class })
CouchbaseTemplate couchbaseTemplate(CouchbaseDataProperties properties, ClusterInfo clusterInfo, Bucket bucket,
MappingCouchbaseConverter mappingCouchbaseConverter, TranslationService translationService) {
CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, bucket, mappingCouchbaseConverter,
translationService);
template.setDefaultConsistency(properties.getConsistency());
return template;
}
@Bean(name = BeanNames.COUCHBASE_OPERATIONS_MAPPING)
@ConditionalOnMissingBean(name = BeanNames.COUCHBASE_OPERATIONS_MAPPING)
@ConditionalOnSingleCandidate(CouchbaseTemplate.class)
RepositoryOperationsMapping repositoryOperationsMapping(CouchbaseTemplate couchbaseTemplate) {
return new RepositoryOperationsMapping(couchbaseTemplate);
}
}

View File

@ -17,7 +17,6 @@
package org.springframework.boot.autoconfigure.data.couchbase;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.couchbase.core.query.Consistency;
/**
* Configuration properties for Spring Data Couchbase.
@ -35,9 +34,14 @@ public class CouchbaseDataProperties {
private boolean autoIndex;
/**
* Consistency to apply by default on generated queries.
* Name of the bucket to connect to.
*/
private Consistency consistency = Consistency.READ_YOUR_OWN_WRITES;
private String bucketName;
/**
* Name of the scope used for all collection access.
*/
private String scopeName;
/**
* Fully qualified name of the FieldNamingStrategy to use.
@ -58,12 +62,20 @@ public class CouchbaseDataProperties {
this.autoIndex = autoIndex;
}
public Consistency getConsistency() {
return this.consistency;
public String getBucketName() {
return this.bucketName;
}
public void setConsistency(Consistency consistency) {
this.consistency = consistency;
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
public String getScopeName() {
return this.scopeName;
}
public void setScopeName(String scopeName) {
this.scopeName = scopeName;
}
public Class<?> getFieldNamingStrategy() {

View File

@ -16,7 +16,7 @@
package org.springframework.boot.autoconfigure.data.couchbase;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import reactor.core.publisher.Flux;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -34,7 +34,7 @@ import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository
* @since 2.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Bucket.class, ReactiveCouchbaseRepository.class, Flux.class })
@ConditionalOnClass({ Cluster.class, ReactiveCouchbaseRepository.class, Flux.class })
@AutoConfigureAfter(CouchbaseDataAutoConfiguration.class)
@Import(CouchbaseReactiveDataConfiguration.class)
public class CouchbaseReactiveDataAutoConfiguration {

View File

@ -16,18 +16,14 @@
package org.springframework.boot.autoconfigure.data.couchbase;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.cluster.ClusterInfo;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
/**
@ -36,24 +32,20 @@ import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOp
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(CouchbaseClientFactory.class)
class CouchbaseReactiveDataConfiguration {
@Bean(name = BeanNames.RXJAVA1_COUCHBASE_TEMPLATE)
@ConditionalOnMissingBean(name = BeanNames.RXJAVA1_COUCHBASE_TEMPLATE)
@ConditionalOnBean({ ClusterInfo.class, Bucket.class })
RxJavaCouchbaseTemplate reactiveCouchbaseTemplate(CouchbaseDataProperties properties, ClusterInfo clusterInfo,
Bucket bucket, MappingCouchbaseConverter mappingCouchbaseConverter, TranslationService translationService) {
RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(clusterInfo, bucket, mappingCouchbaseConverter,
translationService);
template.setDefaultConsistency(properties.getConsistency());
return template;
@Bean(name = BeanNames.REACTIVE_COUCHBASE_TEMPLATE)
@ConditionalOnMissingBean(name = BeanNames.REACTIVE_COUCHBASE_TEMPLATE)
ReactiveCouchbaseTemplate reactiveCouchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
MappingCouchbaseConverter mappingCouchbaseConverter) {
return new ReactiveCouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter);
}
@Bean(name = BeanNames.REACTIVE_COUCHBASE_OPERATIONS_MAPPING)
@ConditionalOnMissingBean(name = BeanNames.REACTIVE_COUCHBASE_OPERATIONS_MAPPING)
@ConditionalOnSingleCandidate(RxJavaCouchbaseTemplate.class)
ReactiveRepositoryOperationsMapping reactiveRepositoryOperationsMapping(
RxJavaCouchbaseTemplate reactiveCouchbaseTemplate) {
ReactiveRepositoryOperationsMapping reactiveCouchbaseRepositoryOperationsMapping(
ReactiveCouchbaseTemplate reactiveCouchbaseTemplate) {
return new ReactiveRepositoryOperationsMapping(reactiveCouchbaseTemplate);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -16,7 +16,7 @@
package org.springframework.boot.autoconfigure.data.couchbase;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import reactor.core.publisher.Flux;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -40,7 +40,7 @@ import org.springframework.data.couchbase.repository.support.ReactiveCouchbaseRe
* @since 2.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Bucket.class, ReactiveCouchbaseRepository.class, Flux.class })
@ConditionalOnClass({ Cluster.class, ReactiveCouchbaseRepository.class, Flux.class })
@ConditionalOnRepositoryType(store = "couchbase", type = RepositoryType.REACTIVE)
@ConditionalOnBean(ReactiveRepositoryOperationsMapping.class)
@ConditionalOnMissingBean(ReactiveCouchbaseRepositoryFactoryBean.class)

View File

@ -328,10 +328,6 @@
"level": "error"
}
},
{
"name": "spring.data.couchbase.consistency",
"defaultValue": "read-your-own-writes"
},
{
"name": "spring.data.couchbase.repositories.type",
"type": "org.springframework.boot.autoconfigure.data.RepositoryType",
@ -1594,6 +1590,101 @@
"level": "error"
}
},
{
"name": "spring.couchbase.bootstrap-hosts",
"type": "java.util.List<java.lang.String>",
"description": "Couchbase nodes (host or IP address) to bootstrap from.",
"deprecation": {
"replacement": "spring.couchbase.connection-string",
"level": "error"
}
},
{
"name": "spring.couchbase.bucket.name",
"type": "java.lang.String",
"description": "Name of the bucket to connect to.",
"deprecation": {
"reason": "A bucket is no longer auto-configured.",
"level": "error"
}
},
{
"name": "spring.couchbase.bucket.password",
"type": "java.lang.String",
"description": "Password of the bucket.",
"deprecation": {
"reason": "A bucket is no longer auto-configured.",
"level": "error"
}
},
{
"name": "spring.couchbase.env.bootstrap.http-direct-port",
"type": "java.lang.Integer",
"description": "Port for the HTTP bootstrap.",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.couchbase.env.bootstrap.http-ssl-port",
"type": "java.lang.Integer",
"description": "Port for the HTTPS bootstrap.",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.couchbase.env.endpoints.key-value",
"type": "java.lang.Integer",
"description": "Number of sockets per node against the key/value service.",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.couchbase.env.endpoints.queryservice.max-endpoints",
"type": "java.lang.Integer",
"description": "Maximum number of sockets per node.",
"deprecation": {
"replacement": "spring.couchbase.env.io.max-endpoints",
"level": "error"
}
},
{
"name": "spring.couchbase.env.endpoints.queryservice.min-endpoints",
"type": "java.lang.Integer",
"description": "Minimum number of sockets per node.",
"deprecation": {
"replacement": "spring.couchbase.env.io.min-endpoints",
"level": "error"
}
},
{
"name": "spring.couchbase.env.endpoints.viewservice.max-endpoints",
"type": "java.lang.Integer",
"description": "Maximum number of sockets per node.",
"deprecation": {
"replacement": "spring.couchbase.env.io.max-endpoints",
"level": "error"
}
},
{
"name": "spring.couchbase.env.endpoints.viewservice.min-endpoints",
"type": "java.lang.Integer",
"description": "Minimum number of sockets per node.",
"deprecation": {
"replacement": "spring.couchbase.env.io.min-endpoints",
"level": "error"
}
},
{
"name": "spring.couchbase.env.timeouts.socket-connect",
"type": "java.time.Duration",
"description": "Socket connect connections timeout.",
"deprecation": {
"level": "error"
}
},
{
"name": "spring.couchbase.env.endpoints.query",
"type": "java.lang.Integer",
@ -1655,6 +1746,14 @@
"level": "error"
}
},
{
"name": "spring.data.couchbase.consistency",
"type": "org.springframework.data.couchbase.core.query.Consistency",
"deprecation": {
"replacement": "spring.data.cassandra.read-timeout",
"level": "error"
}
},
{
"name": "spring.data.neo4j.compiler",
"type": "java.lang.String",

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -21,7 +21,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.couchbase.client.spring.cache.CouchbaseCacheManager;
import com.hazelcast.spring.cache.HazelcastCacheManager;
import org.infinispan.spring.embedded.provider.SpringEmbeddedCacheManager;
@ -36,6 +35,7 @@ import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.cache.CouchbaseCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager;
import static org.assertj.core.api.Assertions.assertThat;

View File

@ -26,10 +26,6 @@ import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.Duration;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.bucket.BucketManager;
import com.couchbase.client.spring.cache.CouchbaseCache;
import com.couchbase.client.spring.cache.CouchbaseCacheManager;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.CaffeineSpec;
import com.hazelcast.cache.HazelcastCachingProvider;
@ -66,6 +62,9 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.cache.CouchbaseCache;
import org.springframework.data.couchbase.cache.CouchbaseCacheManager;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@ -220,8 +219,7 @@ class CacheAutoConfigurationTests extends AbstractCacheAutoConfigurationTests {
assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar");
Cache cache = cacheManager.getCache("foo");
assertThat(cache).isInstanceOf(CouchbaseCache.class);
assertThat(((CouchbaseCache) cache).getTtl()).isEqualTo(0);
assertThat(((CouchbaseCache) cache).getNativeCache()).isEqualTo(context.getBean("bucket"));
assertThat(((CouchbaseCache) cache).getCacheConfiguration().getExpiry()).hasSeconds(0);
});
}
@ -235,8 +233,7 @@ class CacheAutoConfigurationTests extends AbstractCacheAutoConfigurationTests {
assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar");
Cache cache = cacheManager.getCache("foo");
assertThat(cache).isInstanceOf(CouchbaseCache.class);
assertThat(((CouchbaseCache) cache).getTtl()).isEqualTo(2);
assertThat(((CouchbaseCache) cache).getNativeCache()).isEqualTo(context.getBean("bucket"));
assertThat(((CouchbaseCache) cache).getCacheConfiguration().getExpiry()).hasSeconds(2);
});
}
@ -725,11 +722,8 @@ class CacheAutoConfigurationTests extends AbstractCacheAutoConfigurationTests {
static class CouchbaseCacheConfiguration {
@Bean
Bucket bucket() {
BucketManager bucketManager = mock(BucketManager.class);
Bucket bucket = mock(Bucket.class);
given(bucket.bucketManager()).willReturn(bucketManager);
return bucket;
CouchbaseClientFactory couchbaseClientFactory() {
return mock(CouchbaseClientFactory.class);
}
}

View File

@ -17,31 +17,21 @@
package org.springframework.boot.autoconfigure.couchbase;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import com.couchbase.client.core.diagnostics.ClusterState;
import com.couchbase.client.core.diagnostics.DiagnosticsResult;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.bucket.BucketType;
import com.couchbase.client.java.cluster.BucketSettings;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.cluster.DefaultBucketSettings;
import com.couchbase.client.java.cluster.UserRole;
import com.couchbase.client.java.cluster.UserSettings;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import org.junit.jupiter.api.BeforeAll;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.manager.bucket.BucketSettings;
import org.junit.jupiter.api.Test;
import org.testcontainers.couchbase.CouchbaseContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Integration tests for {@link CouchbaseAutoConfiguration}.
@ -52,45 +42,29 @@ import static org.mockito.Mockito.mock;
@Testcontainers(disabledWithoutDocker = true)
class CouchbaseAutoConfigurationIntegrationTests {
private static final String BUCKET_NAME = "cbbucket";
@Container
static final CouchbaseContainer couchbase = new CouchbaseContainer().withClusterAdmin("spring", "password")
.withStartupAttempts(5).withStartupTimeout(Duration.ofMinutes(10));
.withStartupAttempts(5).withStartupTimeout(Duration.ofMinutes(10))
.withNewBucket(BucketSettings.create(BUCKET_NAME));
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CouchbaseAutoConfiguration.class))
.withPropertyValues("spring.couchbase.bootstrap-hosts=localhost",
"spring.couchbase.env.bootstrap.http-direct-port:" + couchbase.getMappedPort(8091),
.withPropertyValues("spring.couchbase.connection-string:localhost:" + couchbase.getMappedPort(11210),
"spring.couchbase.username:spring", "spring.couchbase.password:password",
"spring.couchbase.bucket.name:default");
@BeforeAll
static void createBucket() {
BucketSettings bucketSettings = DefaultBucketSettings.builder().enableFlush(true).name("default")
.password("password").quota(100).replicas(0).type(BucketType.COUCHBASE).build();
List<UserRole> userSettings = Collections.singletonList(new UserRole("admin"));
couchbase.createBucket(bucketSettings,
UserSettings.build().password(bucketSettings.password()).roles(userSettings), true);
}
"spring.couchbase.bucket.name:" + BUCKET_NAME);
@Test
void defaultConfiguration() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(CouchbaseEnvironment.class)
.hasSingleBean(Cluster.class).hasSingleBean(ClusterInfo.class).hasSingleBean(Bucket.class));
}
@Configuration(proxyBeanMethods = false)
static class CustomConfiguration {
@Bean
Cluster myCustomCouchbaseCluster() {
return mock(Cluster.class);
}
@Bean
Bucket myCustomCouchbaseClient() {
return mock(Bucket.class);
}
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(Cluster.class).hasSingleBean(ClusterEnvironment.class);
Cluster cluster = context.getBean(Cluster.class);
Bucket bucket = cluster.bucket(BUCKET_NAME);
bucket.waitUntilReady(Duration.ofMinutes(5));
DiagnosticsResult diagnostics = cluster.diagnostics();
assertThat(diagnostics.state()).isEqualTo(ClusterState.ONLINE);
});
}
}

View File

@ -16,11 +16,14 @@
package org.springframework.boot.autoconfigure.couchbase;
import java.time.Duration;
import java.util.function.Consumer;
import com.couchbase.client.core.env.IoConfig;
import com.couchbase.client.core.env.SecurityConfig;
import com.couchbase.client.core.env.TimeoutConfig;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import com.couchbase.client.java.env.ClusterEnvironment;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -42,119 +45,91 @@ class CouchbaseAutoConfigurationTests {
.withConfiguration(AutoConfigurations.of(CouchbaseAutoConfiguration.class));
@Test
void bootstrapHostsIsRequired() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(CouchbaseEnvironment.class)
void connectionStringIsRequired() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ClusterEnvironment.class)
.doesNotHaveBean(Cluster.class));
}
@Test
void bootstrapHostsCreateEnvironmentAndCluster() {
void connectionStringCreateEnvironmentAndCluster() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
.withPropertyValues("spring.couchbase.bootstrap-hosts=localhost").run((context) -> {
assertThat(context).hasSingleBean(CouchbaseEnvironment.class).hasSingleBean(Cluster.class);
.withPropertyValues("spring.couchbase.connection-string=localhost").run((context) -> {
assertThat(context).hasSingleBean(ClusterEnvironment.class).hasSingleBean(Cluster.class);
assertThat(context.getBean(Cluster.class))
.isSameAs(context.getBean(CouchbaseTestConfiguration.class).couchbaseCluster());
});
}
@Test
void customizeEnvEndpoints() {
testCouchbaseEnv((env) -> {
assertThat(env.kvServiceConfig().minEndpoints()).isEqualTo(2);
assertThat(env.kvServiceConfig().maxEndpoints()).isEqualTo(2);
assertThat(env.queryServiceConfig().minEndpoints()).isEqualTo(3);
assertThat(env.queryServiceConfig().maxEndpoints()).isEqualTo(5);
assertThat(env.viewServiceConfig().minEndpoints()).isEqualTo(4);
assertThat(env.viewServiceConfig().maxEndpoints()).isEqualTo(6);
}, "spring.couchbase.env.endpoints.key-value=2", "spring.couchbase.env.endpoints.queryservice.min-endpoints=3",
"spring.couchbase.env.endpoints.queryservice.max-endpoints=5",
"spring.couchbase.env.endpoints.viewservice.min-endpoints=4",
"spring.couchbase.env.endpoints.viewservice.max-endpoints=6");
}
@Test
void customizeEnvEndpointsUsesNewInfrastructure() {
testCouchbaseEnv((env) -> {
assertThat(env.queryServiceConfig().minEndpoints()).isEqualTo(3);
assertThat(env.queryServiceConfig().maxEndpoints()).isEqualTo(5);
assertThat(env.viewServiceConfig().minEndpoints()).isEqualTo(4);
assertThat(env.viewServiceConfig().maxEndpoints()).isEqualTo(6);
}, "spring.couchbase.env.endpoints.queryservice.min-endpoints=3",
"spring.couchbase.env.endpoints.queryservice.max-endpoints=5",
"spring.couchbase.env.endpoints.viewservice.min-endpoints=4",
"spring.couchbase.env.endpoints.viewservice.max-endpoints=6");
}
@Test
void customizeEnvEndpointsUsesNewInfrastructureWithOnlyMax() {
testCouchbaseEnv((env) -> {
assertThat(env.queryServiceConfig().minEndpoints()).isEqualTo(1);
assertThat(env.queryServiceConfig().maxEndpoints()).isEqualTo(5);
assertThat(env.viewServiceConfig().minEndpoints()).isEqualTo(1);
assertThat(env.viewServiceConfig().maxEndpoints()).isEqualTo(6);
}, "spring.couchbase.env.endpoints.queryservice.max-endpoints=5",
"spring.couchbase.env.endpoints.viewservice.max-endpoints=6");
void customizeEnvIo() {
testClusterEnvironment((env) -> {
IoConfig ioConfig = env.ioConfig();
assertThat(ioConfig.numKvConnections()).isEqualTo(2);
assertThat(ioConfig.maxHttpConnections()).isEqualTo(5);
assertThat(ioConfig.idleHttpConnectionTimeout()).isEqualTo(Duration.ofSeconds(3));
}, "spring.couchbase.env.io.min-endpoints=2", "spring.couchbase.env.io.max-endpoints=5",
"spring.couchbase.env.io.idle-http-connection-timeout=3s");
}
@Test
void customizeEnvTimeouts() {
testCouchbaseEnv((env) -> {
assertThat(env.connectTimeout()).isEqualTo(100);
assertThat(env.kvTimeout()).isEqualTo(200);
assertThat(env.queryTimeout()).isEqualTo(300);
assertThat(env.socketConnectTimeout()).isEqualTo(400);
assertThat(env.viewTimeout()).isEqualTo(500);
}, "spring.couchbase.env.timeouts.connect=100", "spring.couchbase.env.timeouts.keyValue=200",
"spring.couchbase.env.timeouts.query=300", "spring.couchbase.env.timeouts.socket-connect=400",
"spring.couchbase.env.timeouts.view=500");
testClusterEnvironment((env) -> {
TimeoutConfig timeoutConfig = env.timeoutConfig();
assertThat(timeoutConfig.connectTimeout()).isEqualTo(Duration.ofSeconds(1));
assertThat(timeoutConfig.kvTimeout()).isEqualTo(Duration.ofMillis(500));
assertThat(timeoutConfig.queryTimeout()).isEqualTo(Duration.ofSeconds(3));
assertThat(timeoutConfig.viewTimeout()).isEqualTo(Duration.ofSeconds(4));
}, "spring.couchbase.env.timeouts.connect=1s", "spring.couchbase.env.timeouts.key-value=500ms",
"spring.couchbase.env.timeouts.query=3s", "spring.couchbase.env.timeouts.view=4s");
}
@Test
void enableSslNoEnabledFlag() {
testCouchbaseEnv((env) -> {
assertThat(env.sslEnabled()).isTrue();
assertThat(env.sslKeystoreFile()).isEqualTo("foo");
assertThat(env.sslKeystorePassword()).isEqualTo("secret");
}, "spring.couchbase.env.ssl.keyStore=foo", "spring.couchbase.env.ssl.keyStorePassword=secret");
testClusterEnvironment((env) -> {
SecurityConfig securityConfig = env.securityConfig();
assertThat(securityConfig.tlsEnabled()).isTrue();
assertThat(securityConfig.trustManagerFactory()).isNotNull();
}, "spring.couchbase.env.ssl.keyStore=classpath:test.jks", "spring.couchbase.env.ssl.keyStorePassword=secret");
}
@Test
void disableSslEvenWithKeyStore() {
testCouchbaseEnv((env) -> {
assertThat(env.sslEnabled()).isFalse();
assertThat(env.sslKeystoreFile()).isNull();
assertThat(env.sslKeystorePassword()).isNull();
}, "spring.couchbase.env.ssl.enabled=false", "spring.couchbase.env.ssl.keyStore=foo",
testClusterEnvironment((env) -> {
SecurityConfig securityConfig = env.securityConfig();
assertThat(securityConfig.tlsEnabled()).isFalse();
assertThat(securityConfig.trustManagerFactory()).isNull();
}, "spring.couchbase.env.ssl.enabled=false", "spring.couchbase.env.ssl.keyStore=classpath:test.jks",
"spring.couchbase.env.ssl.keyStorePassword=secret");
}
private void testCouchbaseEnv(Consumer<DefaultCouchbaseEnvironment> environmentConsumer, String... environment) {
private void testClusterEnvironment(Consumer<ClusterEnvironment> environmentConsumer, String... environment) {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
.withPropertyValues("spring.couchbase.bootstrap-hosts=localhost").withPropertyValues(environment)
.run((context) -> environmentConsumer.accept(context.getBean(DefaultCouchbaseEnvironment.class)));
.withPropertyValues("spring.couchbase.connection-string=localhost").withPropertyValues(environment)
.run((context) -> environmentConsumer.accept(context.getBean(ClusterEnvironment.class)));
}
@Test
void customizeEnvWithCustomCouchbaseConfiguration() {
this.contextRunner
.withUserConfiguration(CouchbaseTestConfiguration.class,
CouchbaseEnvironmentCustomizerConfiguration.class)
.withPropertyValues("spring.couchbase.bootstrap-hosts=localhost",
ClusterEnvironmentCustomizerConfiguration.class)
.withPropertyValues("spring.couchbase.connection-string=localhost",
"spring.couchbase.env.timeouts.connect=100")
.run((context) -> {
assertThat(context).hasSingleBean(DefaultCouchbaseEnvironment.class);
DefaultCouchbaseEnvironment env = context.getBean(DefaultCouchbaseEnvironment.class);
assertThat(env.socketConnectTimeout()).isEqualTo(5000);
assertThat(env.connectTimeout()).isEqualTo(2000);
assertThat(context).hasSingleBean(ClusterEnvironment.class);
ClusterEnvironment env = context.getBean(ClusterEnvironment.class);
assertThat(env.timeoutConfig().kvTimeout()).isEqualTo(Duration.ofSeconds(5));
assertThat(env.timeoutConfig().connectTimeout()).isEqualTo(Duration.ofSeconds(2));
});
}
@Configuration(proxyBeanMethods = false)
static class CouchbaseEnvironmentCustomizerConfiguration {
static class ClusterEnvironmentCustomizerConfiguration {
@Bean
CouchbaseEnvironmentBuilderCustomizer couchbaseEnvironmentBuilderCustomizer() {
return (builder) -> builder.socketConnectTimeout(5000).connectTimeout(2000);
ClusterEnvironmentBuilderCustomizer clusterEnvironmentBuilderCustomizer() {
return (builder) -> builder.timeoutConfig().kvTimeout(Duration.ofSeconds(5))
.connectTimeout(Duration.ofSeconds(2));
}
}

View File

@ -0,0 +1,504 @@
/*
* Copyright 2012-2020 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 java.io.DataOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.couchbase.client.core.env.SeedNode;
import com.couchbase.client.core.env.TimeoutConfig;
import com.couchbase.client.core.util.UrlQueryStringBuilder;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.ClusterOptions;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.manager.bucket.BucketSettings;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import org.apache.commons.compress.utils.Sets;
import org.jetbrains.annotations.NotNull;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.SocatContainer;
import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
import org.testcontainers.images.builder.Transferable;
import org.testcontainers.shaded.org.apache.commons.io.IOUtils;
import org.testcontainers.utility.ThrowingFunction;
import static java.net.HttpURLConnection.HTTP_OK;
import static org.testcontainers.shaded.org.apache.commons.codec.binary.Base64.encodeBase64String;
/**
* Temporary copy of TestContainers's Couchbase support until it works against Couchbase
* SDK v3.
*/
class CouchbaseContainer extends GenericContainer<CouchbaseContainer> {
public static final String VERSION = "5.5.1";
public static final String DOCKER_IMAGE_NAME = "couchbase/server:";
public static final ObjectMapper MAPPER = new ObjectMapper();
public static final String STATIC_CONFIG = "/opt/couchbase/etc/couchbase/static_config";
public static final String CAPI_CONFIG = "/opt/couchbase/etc/couchdb/default.d/capi.ini";
private static final int REQUIRED_DEFAULT_PASSWORD_LENGTH = 6;
private String memoryQuota = "300";
private String indexMemoryQuota = "300";
private String clusterUsername = "Administrator";
private String clusterPassword = "password";
private boolean keyValue = true;
private boolean query = true;
private boolean index = true;
private boolean primaryIndex = false;
private boolean fts = false;
private ClusterEnvironment couchbaseEnvironment;
private Cluster couchbaseCluster;
private final List<BucketAndUserSettings> newBuckets = new ArrayList<>();
private String urlBase;
private SocatContainer proxy;
CouchbaseContainer() {
this(DOCKER_IMAGE_NAME + VERSION);
}
CouchbaseContainer(String imageName) {
super(imageName);
withNetwork(Network.SHARED);
setWaitStrategy(new HttpWaitStrategy().forPath("/ui/index.html"));
}
@Override
public Set<Integer> getLivenessCheckPortNumbers() {
return Sets.newHashSet(getMappedPort(CouchbasePort.REST));
}
@Override
protected void configure() {
if (this.clusterPassword.length() < REQUIRED_DEFAULT_PASSWORD_LENGTH) {
logger().warn("The provided cluster admin password length is less then the default password policy length. "
+ "Cluster start will fail if configured password requirements are not met.");
}
}
@Override
protected void doStart() {
startProxy(getNetworkAliases().get(0));
try {
super.doStart();
}
catch (Throwable e) {
this.proxy.stop();
throw e;
}
}
private void startProxy(String networkAlias) {
this.proxy = new SocatContainer().withNetwork(getNetwork());
for (CouchbasePort port : CouchbaseContainer.CouchbasePort.values()) {
if (port.isDynamic()) {
this.proxy.withTarget(port.getOriginalPort(), networkAlias);
}
else {
this.proxy.addExposedPort(port.getOriginalPort());
}
}
this.proxy.setWaitStrategy(null);
this.proxy.start();
ExecCreateCmdResponse createCmdResponse = this.dockerClient.execCreateCmd(this.proxy.getContainerId())
.withCmd("sh", "-c",
Stream.of(CouchbaseContainer.CouchbasePort.values())
.map(port -> "/usr/bin/socat " + "TCP-LISTEN:" + port.getOriginalPort()
+ ",fork,reuseaddr " + "TCP:" + networkAlias + ":" + getMappedPort(port))
.collect(Collectors.joining(" & ", "true", "")))
.exec();
try {
this.dockerClient.execStartCmd(createCmdResponse.getId()).exec(new ExecStartResultCallback())
.awaitCompletion(10, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
throw new RuntimeException("Interrupted docker start", e);
}
}
@Override
public List<Integer> getExposedPorts() {
return this.proxy.getExposedPorts();
}
@Override
public String getContainerIpAddress() {
return this.proxy.getContainerIpAddress();
}
@Override
public Integer getMappedPort(int originalPort) {
return this.proxy.getMappedPort(originalPort);
}
protected Integer getMappedPort(CouchbasePort port) {
return getMappedPort(port.getOriginalPort());
}
@Override
public List<Integer> getBoundPortNumbers() {
return this.proxy.getBoundPortNumbers();
}
@Override
@SuppressWarnings({ "unchecked", "ConstantConditions" })
public void stop() {
try {
stopCluster();
this.couchbaseCluster = null;
this.couchbaseEnvironment = null;
}
finally {
Stream.<Runnable>of(super::stop, this.proxy::stop).parallel().forEach(Runnable::run);
}
}
private void stopCluster() {
getCouchbaseCluster().disconnect();
getCouchbaseEnvironment().shutdown();
}
CouchbaseContainer withNewBucket(BucketSettings bucketSettings) {
this.newBuckets.add(new BucketAndUserSettings(bucketSettings));
return self();
}
private void initUrlBase() {
if (this.urlBase == null) {
this.urlBase = String.format("http://%s:%s", getContainerIpAddress(), getMappedPort(CouchbasePort.REST));
}
}
void initCluster() throws Exception {
String poolURL = "/pools/default";
String poolPayload = "memoryQuota=" + URLEncoder.encode(this.memoryQuota, "UTF-8") + "&indexMemoryQuota="
+ URLEncoder.encode(this.indexMemoryQuota, "UTF-8");
String setupServicesURL = "/node/controller/setupServices";
StringBuilder servicePayloadBuilder = new StringBuilder();
if (this.keyValue) {
servicePayloadBuilder.append("kv,");
}
if (this.query) {
servicePayloadBuilder.append("n1ql,");
}
if (this.index) {
servicePayloadBuilder.append("index,");
}
if (this.fts) {
servicePayloadBuilder.append("fts,");
}
String setupServiceContent = "services=" + URLEncoder.encode(servicePayloadBuilder.toString(), "UTF-8");
String webSettingsURL = "/settings/web";
String webSettingsContent = "username=" + URLEncoder.encode(this.clusterUsername, "UTF-8") + "&password="
+ URLEncoder.encode(this.clusterPassword, "UTF-8") + "&port=8091";
callCouchbaseRestAPI(poolURL, poolPayload);
callCouchbaseRestAPI(setupServicesURL, setupServiceContent);
callCouchbaseRestAPI(webSettingsURL, webSettingsContent);
createNodeWaitStrategy().waitUntilReady(this);
callCouchbaseRestAPI("/settings/indexes",
"indexerThreads=0&logLevel=info&maxRollbackPoints=5&storageMode=memory_optimized");
}
@NotNull
private HttpWaitStrategy createNodeWaitStrategy() {
return new HttpWaitStrategy().forPath("/pools/default/")
.withBasicCredentials(this.clusterUsername, this.clusterPassword).forStatusCode(HTTP_OK)
.forResponsePredicate(response -> {
try {
return Optional.of(MAPPER.readTree(response)).map(n -> n.at("/nodes/0/status"))
.map(JsonNode::asText).map("healthy"::equals).orElse(false);
}
catch (IOException e) {
logger().error("Unable to parse response {}", response);
return false;
}
});
}
void createBucket(BucketSettings bucketSetting, boolean primaryIndex) throws IOException {
// Insert Bucket
String payload = convertSettingsToParams(bucketSetting, false).build();
callCouchbaseRestAPI("/pools/default/buckets/", payload);
// Check that the bucket is ready before moving on
new HttpWaitStrategy().forPath("/pools/default/buckets/" + bucketSetting.name())
.withBasicCredentials(this.clusterUsername, this.clusterPassword).forStatusCode(HTTP_OK)
.waitUntilReady(this);
if (this.index && this.primaryIndex) {
Bucket bucket = getCouchbaseCluster().bucket(bucketSetting.name());
bucket.waitUntilReady(Duration.ofSeconds(10));
if (primaryIndex) {
getCouchbaseCluster().queryIndexes().createPrimaryIndex(bucketSetting.name());
}
}
}
void callCouchbaseRestAPI(String url, String payload) throws IOException {
initUrlBase();
String fullUrl = this.urlBase + url;
HttpURLConnection httpConnection = (HttpURLConnection) ((new URL(fullUrl).openConnection()));
httpConnection.setDoOutput(true);
httpConnection.setRequestMethod("POST");
httpConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
String encoded = encodeBase64String(
(this.clusterUsername + ":" + this.clusterPassword).getBytes(StandardCharsets.UTF_8));
httpConnection.setRequestProperty("Authorization", "Basic " + encoded);
DataOutputStream out = new DataOutputStream(httpConnection.getOutputStream());
out.writeBytes(payload);
out.flush();
httpConnection.getResponseCode();
}
@Override
protected void containerIsCreated(String containerId) {
patchConfig(STATIC_CONFIG, this::addMappedPorts);
// capi needs a special configuration, see
// https://developer.couchbase.com/documentation/server/current/install/install-ports.html
patchConfig(CAPI_CONFIG, this::replaceCapiPort);
}
private void patchConfig(String configLocation, ThrowingFunction<String, String> patchFunction) {
String patchedConfig = copyFileFromContainer(configLocation,
inputStream -> patchFunction.apply(IOUtils.toString(inputStream, StandardCharsets.UTF_8)));
copyFileToContainer(Transferable.of(patchedConfig.getBytes(StandardCharsets.UTF_8)), configLocation);
}
private String addMappedPorts(String originalConfig) {
String portConfig = Stream.of(CouchbaseContainer.CouchbasePort.values()).filter(port -> !port.isDynamic())
.map(port -> String.format("{%s, %d}.", port.name, getMappedPort(port)))
.collect(Collectors.joining("\n"));
return String.format("%s\n%s", originalConfig, portConfig);
}
private String replaceCapiPort(String originalConfig) {
return Arrays.stream(originalConfig.split("\n"))
.map(s -> (s.matches("port\\s*=\\s*" + CouchbaseContainer.CouchbasePort.CAPI.getOriginalPort()))
? "port = " + getMappedPort(CouchbaseContainer.CouchbasePort.CAPI) : s)
.collect(Collectors.joining("\n"));
}
@Override
protected void containerIsStarted(InspectContainerResponse containerInfo) {
try {
initCluster();
}
catch (Exception e) {
throw new RuntimeException("Could not init cluster", e);
}
if (!this.newBuckets.isEmpty()) {
for (BucketAndUserSettings bucket : this.newBuckets) {
try {
createBucket(bucket.getBucketSettings(), this.primaryIndex);
}
catch (Exception e) {
throw new RuntimeException("Could not create bucket", e);
}
}
}
}
private Cluster createCouchbaseCluster() {
SeedNode seedNode = SeedNode.create(getContainerIpAddress(),
Optional.of(getMappedPort(CouchbaseContainer.CouchbasePort.MEMCACHED)),
Optional.of(getMappedPort(CouchbaseContainer.CouchbasePort.REST)));
return Cluster.connect(new HashSet<>(Collections.singletonList(seedNode)), ClusterOptions
.clusterOptions(this.clusterUsername, this.clusterPassword).environment(getCouchbaseEnvironment()));
}
synchronized ClusterEnvironment getCouchbaseEnvironment() {
if (this.couchbaseEnvironment == null) {
this.couchbaseEnvironment = createCouchbaseEnvironment();
}
return this.couchbaseEnvironment;
}
synchronized Cluster getCouchbaseCluster() {
if (this.couchbaseCluster == null) {
this.couchbaseCluster = createCouchbaseCluster();
}
return this.couchbaseCluster;
}
private ClusterEnvironment createCouchbaseEnvironment() {
return ClusterEnvironment.builder().timeoutConfig(TimeoutConfig.kvTimeout(Duration.ofSeconds(10))).build();
}
CouchbaseContainer withMemoryQuota(String memoryQuota) {
this.memoryQuota = memoryQuota;
return self();
}
CouchbaseContainer withIndexMemoryQuota(String indexMemoryQuota) {
this.indexMemoryQuota = indexMemoryQuota;
return self();
}
CouchbaseContainer withClusterAdmin(String username, String password) {
this.clusterUsername = username;
this.clusterPassword = password;
return self();
}
CouchbaseContainer withKeyValue(boolean keyValue) {
this.keyValue = keyValue;
return self();
}
CouchbaseContainer withQuery(boolean query) {
this.query = query;
return self();
}
CouchbaseContainer withIndex(boolean index) {
this.index = index;
return self();
}
CouchbaseContainer withPrimaryIndex(boolean primaryIndex) {
this.primaryIndex = primaryIndex;
return self();
}
public CouchbaseContainer withFts(boolean fts) {
this.fts = fts;
return self();
}
enum CouchbasePort {
REST("rest_port", 8091, true), CAPI("capi_port", 8092, false), QUERY("query_port", 8093, false), FTS(
"fts_http_port", 8094, false), CBAS("cbas_http_port", 8095, false), EVENTING("eventing_http_port", 8096,
false), MEMCACHED_SSL("memcached_ssl_port", 11207, false), MEMCACHED("memcached_port", 11210,
false), REST_SSL("ssl_rest_port", 18091, true), CAPI_SSL("ssl_capi_port", 18092,
false), QUERY_SSL("ssl_query_port", 18093, false), FTS_SSL("fts_ssl_port",
18094, false), CBAS_SSL("cbas_ssl_port", 18095,
false), EVENTING_SSL("eventing_ssl_port", 18096, false);
final String name;
final int originalPort;
final boolean dynamic;
CouchbasePort(String name, int originalPort, boolean dynamic) {
this.name = name;
this.originalPort = originalPort;
this.dynamic = dynamic;
}
public String getName() {
return this.name;
}
public int getOriginalPort() {
return this.originalPort;
}
public boolean isDynamic() {
return this.dynamic;
}
}
private class BucketAndUserSettings {
private final BucketSettings bucketSettings;
BucketAndUserSettings(BucketSettings bucketSettings) {
this.bucketSettings = bucketSettings;
}
BucketSettings getBucketSettings() {
return this.bucketSettings;
}
}
private UrlQueryStringBuilder convertSettingsToParams(final BucketSettings settings, boolean update) {
UrlQueryStringBuilder params = UrlQueryStringBuilder.createForUrlSafeNames();
params.add("ramQuotaMB", settings.ramQuotaMB());
params.add("replicaNumber", settings.numReplicas());
params.add("flushEnabled", settings.flushEnabled() ? 1 : 0);
params.add("maxTTL", settings.maxTTL());
params.add("evictionPolicy", settings.ejectionPolicy().alias());
params.add("compressionMode", settings.compressionMode().alias());
// The following values must not be changed on update
if (!update) {
params.add("name", settings.name());
params.add("bucketType", settings.bucketType().alias());
params.add("conflictResolutionType", settings.conflictResolutionType().alias());
params.add("replicaIndex", settings.replicaIndexes() ? 1 : 0);
}
return params;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2012-2020 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 com.couchbase.client.core.env.IoConfig;
import com.couchbase.client.core.env.TimeoutConfig;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Io;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties.Timeouts;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link CouchbaseProperties}.
*
* @author Stephane Nicoll
*/
class CouchbasePropertiesTests {
@Test
void ioHaveConsistentDefaults() {
Io io = new CouchbaseProperties().getEnv().getIo();
assertThat(io.getMinEndpoints()).isEqualTo(IoConfig.DEFAULT_NUM_KV_CONNECTIONS);
assertThat(io.getMaxEndpoints()).isEqualTo(IoConfig.DEFAULT_MAX_HTTP_CONNECTIONS);
assertThat(io.getIdleHttpConnectionTimeout()).isEqualTo(IoConfig.DEFAULT_IDLE_HTTP_CONNECTION_TIMEOUT);
}
@Test
void timeoutsHaveConsistentDefaults() {
Timeouts timeouts = new CouchbaseProperties().getEnv().getTimeouts();
assertThat(timeouts.getConnect()).isEqualTo(TimeoutConfig.DEFAULT_CONNECT_TIMEOUT);
assertThat(timeouts.getKeyValue()).isEqualTo(TimeoutConfig.DEFAULT_KV_TIMEOUT);
assertThat(timeouts.getQuery()).isEqualTo(TimeoutConfig.DEFAULT_QUERY_TIMEOUT);
assertThat(timeouts.getView()).isEqualTo(TimeoutConfig.DEFAULT_VIEW_TIMEOUT);
}
}

View File

@ -16,10 +16,7 @@
package org.springframework.boot.autoconfigure.couchbase;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseBucket;
import com.couchbase.client.java.cluster.ClusterInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -32,23 +29,13 @@ import static org.mockito.Mockito.mock;
* @author Stephane Nicoll
*/
@Configuration(proxyBeanMethods = false)
public class CouchbaseTestConfiguration {
class CouchbaseTestConfiguration {
private final Cluster cluster = mock(Cluster.class);
@Bean
public Cluster couchbaseCluster() {
Cluster couchbaseCluster() {
return this.cluster;
}
@Bean
public ClusterInfo couchbaseClusterInfo() {
return mock(ClusterInfo.class);
}
@Bean
public Bucket couchbaseClient() {
return mock(CouchbaseBucket.class);
}
}

View File

@ -1,66 +0,0 @@
/*
* Copyright 2012-2019 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.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OnBootstrapHostsCondition}.
*
* @author Stephane Nicoll
*/
class OnBootstrapHostsConditionTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(TestConfig.class);
@Test
void bootstrapHostsNotDefined() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean("foo"));
}
@Test
void bootstrapHostsDefinedAsCommaSeparated() {
this.contextRunner.withPropertyValues("spring.couchbase.bootstrap-hosts=value1")
.run((context) -> assertThat(context).hasBean("foo"));
}
@Test
void bootstrapHostsDefinedAsList() {
this.contextRunner.withPropertyValues("spring.couchbase.bootstrap-hosts[0]=value1")
.run((context) -> assertThat(context).hasBean("foo"));
}
@Configuration(proxyBeanMethods = false)
@Conditional(OnBootstrapHostsCondition.class)
static class TestConfig {
@Bean
String foo() {
return "foo";
}
}
}

View File

@ -24,7 +24,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseTestConfiguration;
import org.springframework.boot.autoconfigure.data.couchbase.city.City;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
@ -40,8 +39,6 @@ import org.springframework.data.couchbase.core.convert.DefaultCouchbaseTypeMappe
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.event.ValidatingCouchbaseEventListener;
import org.springframework.data.couchbase.core.query.Consistency;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -67,36 +64,6 @@ class CouchbaseDataAutoConfigurationTests {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ValidatingCouchbaseEventListener.class));
}
@Test
void autoIndexIsDisabledByDefault() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class).run((context) -> {
IndexManager indexManager = context.getBean(IndexManager.class);
assertThat(indexManager.isIgnoreViews()).isTrue();
assertThat(indexManager.isIgnoreN1qlPrimary()).isTrue();
assertThat(indexManager.isIgnoreN1qlSecondary()).isTrue();
});
}
@Test
void enableAutoIndex() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
.withPropertyValues("spring.data.couchbase.auto-index=true").run((context) -> {
IndexManager indexManager = context.getBean(IndexManager.class);
assertThat(indexManager.isIgnoreViews()).isFalse();
assertThat(indexManager.isIgnoreN1qlPrimary()).isFalse();
assertThat(indexManager.isIgnoreN1qlSecondary()).isFalse();
});
}
@Test
void changeConsistency() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
.withPropertyValues("spring.data.couchbase.consistency=eventually-consistent").run((context) -> {
CouchbaseTemplate couchbaseTemplate = context.getBean(CouchbaseTemplate.class);
assertThat(couchbaseTemplate.getDefaultConsistency()).isEqualTo(Consistency.EVENTUALLY_CONSISTENT);
});
}
@Test
@SuppressWarnings("unchecked")
void entityScanShouldSetInitialEntitySet() {
@ -110,14 +77,14 @@ class CouchbaseDataAutoConfigurationTests {
@Test
void typeKeyDefault() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
this.contextRunner.withUserConfiguration(CouchbaseMockConfiguration.class)
.run((context) -> assertThat(context.getBean(MappingCouchbaseConverter.class).getTypeKey())
.isEqualTo(DefaultCouchbaseTypeMapper.DEFAULT_TYPE_KEY));
}
@Test
void typeKeyCanBeCustomized() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
this.contextRunner.withUserConfiguration(CouchbaseMockConfiguration.class)
.withPropertyValues("spring.data.couchbase.type-key=_custom")
.run((context) -> assertThat(context.getBean(MappingCouchbaseConverter.class).getTypeKey())
.isEqualTo("_custom"));
@ -134,7 +101,7 @@ class CouchbaseDataAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@Import(CouchbaseTestConfiguration.class)
@Import(CouchbaseMockConfiguration.class)
static class CustomConversionsConfig {
@Bean(BeanNames.COUCHBASE_CUSTOM_CONVERSIONS)
@ -146,7 +113,7 @@ class CouchbaseDataAutoConfigurationTests {
@Configuration(proxyBeanMethods = false)
@EntityScan("org.springframework.boot.autoconfigure.data.couchbase.city")
@Import(CouchbaseTestConfiguration.class)
@Import(CouchbaseMockConfiguration.class)
static class EntityScanConfig {
}

View File

@ -18,7 +18,7 @@ package org.springframework.boot.autoconfigure.data.couchbase;
import org.junit.jupiter.api.Test;
import org.springframework.data.couchbase.config.CouchbaseConfigurationSupport;
import org.springframework.data.couchbase.core.convert.DefaultCouchbaseTypeMapper;
import static org.assertj.core.api.Assertions.assertThat;
@ -31,7 +31,7 @@ class CouchbaseDataPropertiesTests {
@Test
void typeKeyHasConsistentDefault() {
assertThat(new CouchbaseDataProperties().getTypeKey()).isEqualTo(new CouchbaseConfigurationSupport().typeKey());
assertThat(new CouchbaseDataProperties().getTypeKey()).isEqualTo(DefaultCouchbaseTypeMapper.DEFAULT_TYPE_KEY);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -14,22 +14,25 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.couchbase;
package org.springframework.boot.autoconfigure.data.couchbase;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.OnPropertyListCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import static org.mockito.Mockito.mock;
/**
* Condition to determine if {@code spring.couchbase.bootstrap-hosts} is specified.
* Test configuration that mocks access to Couchbase.
*
* @author Stephane Nicoll
* @author Madhura Bhave
* @author Eneias Silva
*/
class OnBootstrapHostsCondition extends OnPropertyListCondition {
@Configuration(proxyBeanMethods = false)
class CouchbaseMockConfiguration {
OnBootstrapHostsCondition() {
super("spring.couchbase.bootstrap-hosts", () -> ConditionMessage.forCondition("Couchbase Bootstrap Hosts"));
@Bean
CouchbaseClientFactory couchbaseClientFactory() {
return mock(CouchbaseClientFactory.class);
}
}

View File

@ -23,7 +23,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseTestConfiguration;
import org.springframework.boot.autoconfigure.data.couchbase.city.CityRepository;
import org.springframework.boot.autoconfigure.data.couchbase.city.ReactiveCityRepository;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -62,7 +61,7 @@ class CouchbaseReactiveAndImperativeRepositoriesAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@Import({ CouchbaseTestConfiguration.class, Registrar.class })
@Import({ CouchbaseMockConfiguration.class, Registrar.class })
static class BaseConfiguration {
}

View File

@ -24,7 +24,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseProperties;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseTestConfiguration;
import org.springframework.boot.autoconfigure.data.couchbase.city.City;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
@ -34,7 +33,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.event.ValidatingCouchbaseEventListener;
@ -56,13 +55,12 @@ class CouchbaseReactiveDataAutoConfigurationTests {
@Test
void disabledIfCouchbaseIsNotConfigured() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(RxJavaCouchbaseTemplate.class));
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveCouchbaseTemplate.class));
}
@Test
void validatorIsPresent() {
this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(ValidatingCouchbaseEventListener.class));
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ValidatingCouchbaseEventListener.class));
}
@Test
@ -79,7 +77,7 @@ class CouchbaseReactiveDataAutoConfigurationTests {
@Test
void customConversions() {
this.contextRunner.withUserConfiguration(CustomConversionsConfig.class).run((context) -> {
RxJavaCouchbaseTemplate template = context.getBean(RxJavaCouchbaseTemplate.class);
ReactiveCouchbaseTemplate template = context.getBean(ReactiveCouchbaseTemplate.class);
assertThat(
template.getConverter().getConversionService().canConvert(CouchbaseProperties.class, Boolean.class))
.isTrue();
@ -87,7 +85,7 @@ class CouchbaseReactiveDataAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@Import(CouchbaseTestConfiguration.class)
@Import(CouchbaseMockConfiguration.class)
static class CustomConversionsConfig {
@Bean(BeanNames.COUCHBASE_CUSTOM_CONVERSIONS)
@ -99,7 +97,7 @@ class CouchbaseReactiveDataAutoConfigurationTests {
@Configuration(proxyBeanMethods = false)
@EntityScan("org.springframework.boot.autoconfigure.data.couchbase.city")
@Import(CouchbaseTestConfiguration.class)
@Import(CouchbaseMockConfiguration.class)
static class EntityScanConfig {
}

View File

@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseTestConfiguration;
import org.springframework.boot.autoconfigure.data.alt.couchbase.CityCouchbaseRepository;
import org.springframework.boot.autoconfigure.data.alt.couchbase.ReactiveCityCouchbaseRepository;
import org.springframework.boot.autoconfigure.data.couchbase.city.City;
@ -86,14 +85,14 @@ class CouchbaseReactiveRepositoriesAutoConfigurationTests {
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(City.class)
@Import(CouchbaseTestConfiguration.class)
@Import(CouchbaseMockConfiguration.class)
static class DefaultConfiguration {
}
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(EmptyDataPackage.class)
@Import(CouchbaseTestConfiguration.class)
@Import(CouchbaseMockConfiguration.class)
static class NoRepositoryConfiguration {
}
@ -101,7 +100,7 @@ class CouchbaseReactiveRepositoriesAutoConfigurationTests {
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(CouchbaseReactiveRepositoriesAutoConfigurationTests.class)
@EnableCouchbaseRepositories(basePackageClasses = CityCouchbaseRepository.class)
@Import(CouchbaseTestConfiguration.class)
@Import(CouchbaseMockConfiguration.class)
static class CustomizedConfiguration {
}

View File

@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseTestConfiguration;
import org.springframework.boot.autoconfigure.data.couchbase.city.City;
import org.springframework.boot.autoconfigure.data.couchbase.city.CityRepository;
import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage;
@ -82,14 +81,14 @@ class CouchbaseRepositoriesAutoConfigurationTests {
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(City.class)
@Import(CouchbaseTestConfiguration.class)
@Import(CouchbaseMockConfiguration.class)
static class DefaultConfiguration {
}
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(EmptyDataPackage.class)
@Import(CouchbaseTestConfiguration.class)
@Import(CouchbaseMockConfiguration.class)
static class NoRepositoryConfiguration {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -16,10 +16,9 @@
package org.springframework.boot.autoconfigure.data.couchbase.city;
import com.couchbase.client.java.repository.annotation.Field;
import com.couchbase.client.java.repository.annotation.Id;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Field;
@Document
public class City {

View File

@ -215,14 +215,7 @@ bom {
]
}
}
library("Couchbase Cache Client", "2.1.0") {
group("com.couchbase.client") {
modules = [
"couchbase-spring-cache"
]
}
}
library("Couchbase Client", "2.7.13") {
library("Couchbase Client", "3.0.2") {
group("com.couchbase.client") {
modules = [
"java-client"
@ -1616,7 +1609,7 @@ bom {
]
}
}
library("Spring Data Releasetrain", "Neumann-M4") {
library("Spring Data Releasetrain", "Neumann-BUILD-SNAPSHOT") {
group("org.springframework.data") {
imports = [
"spring-data-releasetrain"

View File

@ -4473,20 +4473,18 @@ There are `spring-boot-starter-data-couchbase` and `spring-boot-starter-data-cou
[[boot-features-connecting-to-couchbase]]
==== Connecting to Couchbase
You can get a `Bucket` and `Cluster` by adding the Couchbase SDK and some configuration.
You can get a `Cluster` by adding the Couchbase SDK and some configuration.
The `spring.couchbase.*` properties can be used to customize the connection.
Generally, you provide the bootstrap hosts, bucket name, and password, as shown in the following example:
Generally, you provide the https://github.com/couchbaselabs/sdk-rfcs/blob/master/rfc/0011-connection-string.md[connection string], username, and password, as shown in the following example:
[source,properties,indent=0,configprops]
----
spring.couchbase.bootstrap-hosts=my-host-1,192.168.1.123
spring.couchbase.bucket.name=my-bucket
spring.couchbase.bucket.password=secret
spring.couchbase.connection-string=couchbase://192.168.1.123
spring.couchbase.username=user
spring.couchbase.password=secret
----
TIP: You need to provide _at least_ the bootstrap host(s), in which case the bucket name is `default` and the password is an empty String.
It is also possible to customize some of the `CouchbaseEnvironment` settings.
It is also possible to customize some of the `ClusterEnvironment` settings.
For instance, the following configuration changes the timeout to use to open a new `Bucket` and enables SSL support:
[source,properties,indent=0,configprops]
@ -4497,18 +4495,24 @@ For instance, the following configuration changes the timeout to use to open a n
----
TIP: Check the `spring.couchbase.env.*` properties for more details.
To take more control, one or more `CouchbaseEnvironmentBuilderCustomizer` beans can be used.
To take more control, one or more `ClusterEnvironmentBuilderCustomizer` beans can be used.
[[boot-features-spring-data-couchbase-repositories]]
==== Spring Data Couchbase Repositories
Spring Data includes repository support for Couchbase.
For complete details of Spring Data Couchbase, refer to the https://docs.spring.io/spring-data/couchbase/docs/current/reference/html/[reference documentation].
For complete details of Spring Data Couchbase, refer to the {spring-data-couchbase-docs}[reference documentation].
You can inject an auto-configured `CouchbaseTemplate` instance as you would with any other Spring Bean, provided a _default_ `CouchbaseConfigurer` is available (which happens when you enable Couchbase support, as explained earlier).
You can inject an auto-configured `CouchbaseTemplate` instance as you would with any other Spring Bean, provided a `CouchbaseClientFactory` bean is available.
This happens when a `Cluster` is availabe, as described above, and a bucket name as been specified:
The following examples shows how to inject a Couchbase bean:
[source,properties,indent=0,configprops]
----
spring.data.couchbase.bucket-name=my-bucket
----
The following examples shows how to inject a `CouchbaseTemplate` bean:
[source,java,indent=0]
----
@ -4529,9 +4533,9 @@ The following examples shows how to inject a Couchbase bean:
There are a few beans that you can define in your own configuration to override those provided by the auto-configuration:
* A `CouchbaseTemplate` `@Bean` with a name of `couchbaseTemplate`.
* An `IndexManager` `@Bean` with a name of `couchbaseIndexManager`.
* A `CouchbaseMappingContext` `@Bean` with a name of `couchbaseMappingContext`.
* A `CustomConversions` `@Bean` with a name of `couchbaseCustomConversions`.
* A `CouchbaseTemplate` `@Bean` with a name of `couchbaseTemplate`.
To avoid hard-coding those names in your own config, you can reuse `BeanNames` provided by Spring Data Couchbase.
For instance, you can customize the converters to use, as follows:
@ -4551,8 +4555,6 @@ For instance, you can customize the converters to use, as follows:
}
----
TIP: If you want to fully bypass the auto-configuration for Spring Data Couchbase, provide your own implementation of `org.springframework.data.couchbase.config.AbstractCouchbaseDataConfiguration`.
[[boot-features-ldap]]

View File

@ -10,8 +10,7 @@ def caches = [
"com.github.ben-manes.caffeine:caffeine"
],
"couchbase": [
"com.couchbase.client:java-client",
"com.couchbase.client:couchbase-spring-cache"
project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-couchbase")
],
"ehcache": [
"javax.cache:cache-api",

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -16,10 +16,9 @@
package smoketest.data.couchbase;
import com.couchbase.client.java.repository.annotation.Field;
import com.couchbase.client.java.repository.annotation.Id;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Field;
@Document
public class User {

View File

@ -1,6 +1,8 @@
spring.couchbase.bootstrap-hosts=localhost
spring.couchbase.connection-string=couchbase://127.0.0.1
spring.couchbase.username=admin
spring.couchbase.password=secret
spring.couchbase.env.timeouts.connect=10000
spring.couchbase.env.timeouts.socket-connect=10000
spring.couchbase.env.timeouts.connect=15s
spring.data.couchbase.auto-index=true
spring.data.couchbase.bucket-name=default

View File

@ -46,4 +46,5 @@
<suppress files="[\\/]src[\\/]intTest[\\/]java[\\/]" checks="SpringJavadoc" message="\@since" />
<suppress files="LinuxDomainSocket" checks="FinalClass" message="SockaddrUn" />
<suppress files="BsdDomainSocket" checks="FinalClass" message="SockaddrUn" />
<suppress files="[\\/]org.springframework.boot.autoconfigure.couchbase.CouchbaseContainer.java$" checks=".*" />
</suppressions>