diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index f2579c57230..9951e96cb10 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -35,6 +35,11 @@ transactions-jta true + + com.couchbase.client + couchbase-spring-cache + true + com.fasterxml.jackson.core jackson-databind diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.java index 8530bf48c27..f85244bfa2a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfiguration.java @@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.Cache import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration; import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; @@ -64,7 +65,8 @@ import org.springframework.util.Assert; @ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver") @EnableConfigurationProperties(CacheProperties.class) @AutoConfigureBefore(HibernateJpaAutoConfiguration.class) -@AutoConfigureAfter({ HazelcastAutoConfiguration.class, RedisAutoConfiguration.class }) +@AutoConfigureAfter({CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class, + RedisAutoConfiguration.class }) @Import({ CacheManagerCustomizers.class, CacheConfigurationImportSelector.class }) public class CacheAutoConfiguration { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.java index 5245b0c7d20..b5d57911401 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheConfigurations.java @@ -42,6 +42,7 @@ final class CacheConfigurations { mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class); + mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class); mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class); mappings.put(CacheType.GUAVA, GuavaCacheConfiguration.class); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheProperties.java index 84be9062402..df7bdf5e955 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheProperties.java @@ -46,6 +46,8 @@ public class CacheProperties { private final Caffeine caffeine = new Caffeine(); + private final Couchbase couchbase = new Couchbase(); + private final EhCache ehcache = new EhCache(); private final Hazelcast hazelcast = new Hazelcast(); @@ -76,6 +78,10 @@ public class CacheProperties { return this.caffeine; } + public Couchbase getCouchbase() { + return this.couchbase; + } + public EhCache getEhcache() { return this.ehcache; } @@ -133,6 +139,26 @@ public class CacheProperties { } + /** + * Couchbase specific cache properties. + */ + public static class Couchbase { + + /** + * Entry expiration in milliseconds. By default the entries never expire. + */ + private int expiration; + + public int getExpiration() { + return this.expiration; + } + + public void setExpiration(int expiration) { + this.expiration = expiration; + } + + } + /** * EhCache specific cache properties. */ diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.java index 5277c1fc471..0399777eecf 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CacheType.java @@ -51,6 +51,11 @@ public enum CacheType { */ INFINISPAN, + /** + * Couchbase backed caching. + */ + COUCHBASE, + /** * Redis backed caching. */ diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CouchbaseCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CouchbaseCacheConfiguration.java new file mode 100644 index 00000000000..202c8df53dd --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/CouchbaseCacheConfiguration.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2016 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 + * + * http://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.cache; + +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 org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +/** + * Couchbase cache configuration. + * + * @author Stephane Nicoll + * @since 1.4.0 + */ +@Configuration +@ConditionalOnClass({Bucket.class, CouchbaseCacheManager.class}) +@ConditionalOnMissingBean(CacheManager.class) +@ConditionalOnSingleCandidate(Bucket.class) +@Conditional(CacheCondition.class) +public class CouchbaseCacheConfiguration { + + private final CacheProperties cacheProperties; + + private final CacheManagerCustomizers customizers; + + private final Bucket bucket; + + public CouchbaseCacheConfiguration(CacheProperties cacheProperties, + CacheManagerCustomizers customizers, Bucket bucket) { + this.cacheProperties = cacheProperties; + this.customizers = customizers; + this.bucket = bucket; + } + + @Bean + public CouchbaseCacheManager cacheManager() { + List cacheNames = this.cacheProperties.getCacheNames(); + CouchbaseCacheManager cacheManager = new CouchbaseCacheManager( + CacheBuilder.newInstance(this.bucket) + .withExpirationInMillis(this.cacheProperties.getCouchbase().getExpiration()), + cacheNames.toArray(new String[cacheNames.size()])); + return this.customizers.customize(cacheManager); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java index b87b7d85d65..34d928adcec 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/cache/CacheAutoConfigurationTests.java @@ -30,6 +30,10 @@ 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.google.common.cache.CacheBuilder; @@ -207,6 +211,43 @@ public class CacheAutoConfigurationTests { assertThat(cacheManager.getCacheNames()).hasSize(2); } + @Test + public void couchbaseCacheExplicit() { + load(CouchbaseCacheConfiguration.class, "spring.cache.type=couchbase"); + CouchbaseCacheManager cacheManager = validateCacheManager(CouchbaseCacheManager.class); + assertThat(cacheManager.getCacheNames()).isEmpty(); + } + + @Test + public void couchbaseCacheWithCustomizers() { + testCustomizers(CouchbaseCacheAndCustomizersConfiguration.class, "couchbase", + "allCacheManagerCustomizer", "couchbaseCacheManagerCustomizer"); + } + + @Test + public void couchbaseCacheExplicitWithCaches() { + load(CouchbaseCacheConfiguration.class, "spring.cache.type=couchbase", + "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); + CouchbaseCacheManager cacheManager = validateCacheManager(CouchbaseCacheManager.class); + 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(this.context.getBean("bucket")); + } + + @Test + public void couchbaseCacheExplicitWithTtl() { + load(CouchbaseCacheConfiguration.class, "spring.cache.type=couchbase", + "spring.cache.cacheNames=foo,bar", "spring.cache.couchbase.expiration=2000"); + CouchbaseCacheManager cacheManager = validateCacheManager(CouchbaseCacheManager.class); + assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar"); + Cache cache = cacheManager.getCache("foo"); + assertThat(cache).isInstanceOf(CouchbaseCache.class); + assertThat(((CouchbaseCache) cache).getTtl()).isEqualTo(2000); + assertThat(((CouchbaseCache) cache).getNativeCache()).isEqualTo(this.context.getBean("bucket")); + } + @Test public void redisCacheExplicit() { load(RedisCacheConfiguration.class, "spring.cache.type=redis"); @@ -733,6 +774,26 @@ public class CacheAutoConfigurationTests { static class GenericCacheAndCustomizersConfiguration { } + @Configuration + @EnableCaching + static class CouchbaseCacheConfiguration { + + @Bean + public Bucket bucket() { + BucketManager bucketManager = mock(BucketManager.class); + Bucket bucket = mock(Bucket.class); + given(bucket.bucketManager()).willReturn(bucketManager); + return bucket; + } + + } + + @Configuration + @Import({ CouchbaseCacheConfiguration.class, CacheManagerCustomizersConfiguration.class }) + static class CouchbaseCacheAndCustomizersConfiguration { + + } + @Configuration @EnableCaching static class RedisCacheConfiguration { @@ -955,6 +1016,12 @@ public class CacheAutoConfigurationTests { }; } + @Bean + public CacheManagerCustomizer couchbaseCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { + }; + } + @Bean public CacheManagerCustomizer redisCacheManagerCustomizer() { return new CacheManagerTestCustomizer() { diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index aaa30b4990c..424864fa398 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -61,6 +61,7 @@ 1.6 2.4.2 2.2.3 + 2.0.0 1.3.2 10.12.1.1 3.1.2 @@ -525,6 +526,11 @@ java-client ${couchbase-client.version} + + com.couchbase.client + couchbase-spring-cache + ${couchbase-cache-client.version} + com.datastax.cassandra cassandra-driver-core diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index f6148a882a8..8803eb0b9cc 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -64,6 +64,7 @@ content into your application; rather pick only the properties that you need. # SPRING CACHE ({sc-spring-boot-autoconfigure}/cache/CacheProperties.{sc-ext}[CacheProperties]) spring.cache.cache-names= # Comma-separated list of cache names to create if supported by the underlying cache manager. spring.cache.caffeine.spec= # The spec to use to create caches. Check CaffeineSpec for more details on the spec format. + spring.cache.couchbase.expiration=0 # Entry expiration in milliseconds. By default the entries never expire. spring.cache.ehcache.config= # The location of the configuration file to use to initialize EhCache. spring.cache.guava.spec= # The spec to use to create caches. Check CacheBuilderSpec for more details on the spec format. spring.cache.hazelcast.config= # The location of the configuration file to use to initialize Hazelcast. diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index de2bd7a87cf..837fe23ddc4 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3311,6 +3311,7 @@ providers (in this order): * <> * <> * <> +* <> * <> * <> * <> @@ -3320,8 +3321,8 @@ It is also possible to _force_ the cache provider to use via the `spring.cache.t property. If the `CacheManager` is auto-configured by Spring Boot, you can further tune its -configuration before it is fully initialized by exposing a bean implementing the -`CacheManagerCustomizer` interface. The following set the cache names to use. +configuration before it is fully initialized by exposing a bean implementing the +`CacheManagerCustomizer` interface. The following sets the cache names to use. [source,java,indent=0] ---- @@ -3433,6 +3434,56 @@ Caches can be created on startup via the `spring.cache.cache-names` property. If +[[boot-features-caching-provider-couchbase]] +==== Couchbase +If Couchbase is available and <>, a +`CouchbaseCacheManager` is auto-configured. It is also possible to create additional +caches on startup using the `spring.cache.cache-names` property. These will operate on +the `Bucket` that was auto-configured. You can _also_ create additional caches on another +`Bucket` using the customizer: assume you need two caches on the "main" `Bucket` (`foo` +and `bar`) and one `biz` cache with a custom time to live of 2sec on the `another` +`Bucket`. First, you can create the two first caches simply via configuration: + +[source,properties,indent=0] +---- + spring.cache.cache-names=foo,bar +---- + +Then define this extra `@Configuration` to configure the extra `Bucket` and the `biz` +cache: + + +[source,java,indent=0] +---- + @Configuration + public class CouchbaseCacheConfiguration { + + private final Cluster cluster; + + public CouchbaseCacheConfiguration(Cluster cluster) { + this.cluster = cluster; + } + + @Bean + public Bucket anotherBucket() { + return this.cluster.openBucket("another", "secret"); + } + + @Bean + public CacheManagerCustomizer cacheManagerCustomizer() { + return c -> { + c.prepareCache("biz", CacheBuilder.newInstance(anotherBucket()) + .withExpirationInMillis(2000)); + }; + } + + } +---- + +This sample configuration reuses the `Cluster` that was created via auto-configuration. + + + [[boot-features-caching-provider-redis]] ==== Redis If Redis is available and configured, the `RedisCacheManager` is auto-configured. It is diff --git a/spring-boot-samples/spring-boot-sample-cache/README.adoc b/spring-boot-samples/spring-boot-sample-cache/README.adoc index 7ac9474d0fe..d464d72c743 100644 --- a/spring-boot-samples/spring-boot-sample-cache/README.adoc +++ b/spring-boot-samples/spring-boot-sample-cache/README.adoc @@ -7,6 +7,7 @@ abstraction is supported by many caching libraries, including: * `EhCache` * `Hazelcast` * `Infinispan` +* `Couchbase` * `Redis` * `Guava` * Simple provider based on `ConcurrentHashMap` @@ -78,6 +79,12 @@ can set the `spring.cache.infinispan.config` property to use the provided +=== Couchbase +Add the `java-client` and `couchbase-spring-cache` dependencies and make sure that you +have setup at least a `spring.couchbase.bootstrap-hosts` property. + + + === Redis Add the `spring-boot-starter-data-redis` and make sure it is configured properly (by default, a redis instance with the default settings is expected on your local box). diff --git a/spring-boot-samples/spring-boot-sample-cache/pom.xml b/spring-boot-samples/spring-boot-sample-cache/pom.xml index e54fb393238..631787da88c 100644 --- a/spring-boot-samples/spring-boot-sample-cache/pom.xml +++ b/spring-boot-samples/spring-boot-sample-cache/pom.xml @@ -69,6 +69,16 @@ --> +