diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseHealth.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseHealth.java index ea33c912276..005921fd49e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseHealth.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseHealth.java @@ -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 describe(EndpointHealth endpointHealth) { + private Map describe(EndpointDiagnostics endpointHealth) { Map 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; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseHealthIndicator.java index 86e0c3f4e28..cefa92cc542 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseHealthIndicator.java @@ -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); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseReactiveHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseReactiveHealthIndicator.java index 62f5cd1e11b..894178861bb 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseReactiveHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/couchbase/CouchbaseReactiveHealthIndicator.java @@ -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 doHealthCheck(Health.Builder builder) { - DiagnosticsReport diagnostics = this.cluster.diagnostics(); + DiagnosticsResult diagnostics = this.cluster.diagnostics(); new CouchbaseHealth(diagnostics).applyTo(builder); return Mono.just(builder.build()); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/couchbase/CouchbaseHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/couchbase/CouchbaseHealthIndicatorTests.java index c6f00adcbdf..5a3928d3925 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/couchbase/CouchbaseHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/couchbase/CouchbaseHealthIndicatorTests.java @@ -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 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> 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 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> 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); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/couchbase/CouchbaseReactiveHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/couchbase/CouchbaseReactiveHealthIndicatorTests.java index fe3923120bd..30ace183764 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/couchbase/CouchbaseReactiveHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/couchbase/CouchbaseReactiveHealthIndicatorTests.java @@ -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 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> 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 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> 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); diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index 59be6a360a1..07104d004bf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -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") diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CouchbaseCacheConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CouchbaseCacheConfiguration.java index 69c1742f13d..99c1891c726 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CouchbaseCacheConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CouchbaseCacheConfiguration.java @@ -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 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); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseEnvironmentBuilderCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/ClusterEnvironmentBuilderCustomizer.java similarity index 67% rename from spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseEnvironmentBuilderCustomizer.java rename to spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/ClusterEnvironmentBuilderCustomizer.java index 2af76933c8c..d5f2234a401 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseEnvironmentBuilderCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/ClusterEnvironmentBuilderCustomizer.java @@ -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); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java index 5351ddef471..e82f6214e9a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfiguration.java @@ -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 customizers) { + @ConditionalOnMissingBean + public ClusterEnvironment couchbaseClusterEnvironment(CouchbaseProperties properties, + ObjectProvider 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; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseProperties.java index 21d548eea10..fd3e11838b5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseProperties.java @@ -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 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 getBootstrapHosts() { - return this.bootstrapHosts; + public String getConnectionString() { + return this.connectionString; } - public void setBootstrapHosts(List 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; - } - - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseClientFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseClientFactoryConfiguration.java new file mode 100644 index 00000000000..f6b7aceb8bb --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseClientFactoryConfiguration.java @@ -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()); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseClientFactoryDependentConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseClientFactoryDependentConfiguration.java new file mode 100644 index 00000000000..f83d9094699 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseClientFactoryDependentConfiguration.java @@ -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); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataAutoConfiguration.java index 2c8d7f36084..48a4f76c47c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataAutoConfiguration.java @@ -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) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataConfiguration.java index f228007b97f..d747615b90e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataConfiguration.java @@ -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); - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataProperties.java index 9f128dff8b1..1230e41af72 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataProperties.java @@ -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() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataAutoConfiguration.java index 9c7d25b8604..104a9419647 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataAutoConfiguration.java @@ -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 { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataConfiguration.java index 341cbd64492..8450cef5f3d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataConfiguration.java @@ -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); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveRepositoriesAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveRepositoriesAutoConfiguration.java index a9a52957c19..c120b9b6b84 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveRepositoriesAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveRepositoriesAutoConfiguration.java @@ -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) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 3246fd0288a..3cd718fed72 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -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", + "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", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/AbstractCacheAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/AbstractCacheAutoConfigurationTests.java index a7cab890b15..b6f85a57184 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/AbstractCacheAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/AbstractCacheAutoConfigurationTests.java @@ -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; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java index 9cb8c627fbd..70cecb92a42 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java @@ -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); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java index b1192f87363..0b4898e3f3f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationIntegrationTests.java @@ -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 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); + }); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java index aac2d9c95e4..6d9a28b2d9c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseAutoConfigurationTests.java @@ -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 environmentConsumer, String... environment) { + private void testClusterEnvironment(Consumer 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)); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseContainer.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseContainer.java new file mode 100644 index 00000000000..bf6efae0892 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseContainer.java @@ -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 { + + 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 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 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 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 getBoundPortNumbers() { + return this.proxy.getBoundPortNumbers(); + } + + @Override + @SuppressWarnings({ "unchecked", "ConstantConditions" }) + public void stop() { + try { + stopCluster(); + this.couchbaseCluster = null; + this.couchbaseEnvironment = null; + } + finally { + Stream.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 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; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbasePropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbasePropertiesTests.java new file mode 100644 index 00000000000..a134b6c42a8 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbasePropertiesTests.java @@ -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); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseTestConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseTestConfiguration.java index 646b00e24bb..b2b1cf8c852 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseTestConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/CouchbaseTestConfiguration.java @@ -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); - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/OnBootstrapHostsConditionTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/OnBootstrapHostsConditionTests.java deleted file mode 100644 index 5f3d3c9bd7b..00000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/couchbase/OnBootstrapHostsConditionTests.java +++ /dev/null @@ -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"; - } - - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataAutoConfigurationTests.java index 25750943c96..6cb4e0698fe 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataAutoConfigurationTests.java @@ -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 { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataPropertiesTests.java index 3a26c7c9229..47d3e3db6d2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseDataPropertiesTests.java @@ -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); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/OnBootstrapHostsCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseMockConfiguration.java similarity index 50% rename from spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/OnBootstrapHostsCondition.java rename to spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseMockConfiguration.java index 98898ede88e..2e9ca2f8212 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/couchbase/OnBootstrapHostsCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseMockConfiguration.java @@ -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); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveAndImperativeRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveAndImperativeRepositoriesAutoConfigurationTests.java index 205b86b7918..6eeaf9a97d9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveAndImperativeRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveAndImperativeRepositoriesAutoConfigurationTests.java @@ -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 { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataAutoConfigurationTests.java index dae5e6b741c..a949f14aa10 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveDataAutoConfigurationTests.java @@ -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 { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveRepositoriesAutoConfigurationTests.java index e98409d89b4..060ea7f0d96 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseReactiveRepositoriesAutoConfigurationTests.java @@ -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 { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseRepositoriesAutoConfigurationTests.java index 22020ca1795..ac628cc4fa9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/CouchbaseRepositoriesAutoConfigurationTests.java @@ -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 { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/city/City.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/city/City.java index f98fecfd94a..51c657d9a02 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/city/City.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/couchbase/city/City.java @@ -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 { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/test.jks b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/test.jks new file mode 100644 index 00000000000..0fc3e802f75 Binary files /dev/null and b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/test.jks differ diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index 95accfa5ffc..eade2c0f52e 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -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" diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc index 676c9dec48b..5f83001ea2d 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc @@ -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]] diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/build.gradle b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/build.gradle index e66059d7695..84e14c23b1f 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/build.gradle +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-cache/build.gradle @@ -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", diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/main/java/smoketest/data/couchbase/User.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/main/java/smoketest/data/couchbase/User.java index c897ce0585d..2f7364c216c 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/main/java/smoketest/data/couchbase/User.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/main/java/smoketest/data/couchbase/User.java @@ -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 { diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/main/resources/application.properties index e1679cc3723..9cc0b988b78 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/main/resources/application.properties +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-couchbase/src/main/resources/application.properties @@ -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 diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index 7eb5d2d0c2e..4ac2e81d186 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -46,4 +46,5 @@ +