From 3c289206e4522a629646597d1c7974639f07ca4a Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 12 Sep 2016 14:45:51 -0700 Subject: [PATCH] Add auto-configuration support for Apache Geode as a caching provider Closes gh-6967 --- spring-boot-autoconfigure/pom.xml | 5 + .../cache/CacheConfigurations.java | 2 + .../boot/autoconfigure/cache/CacheType.java | 8 +- .../cache/GeodeCacheConfiguration.java | 76 +++++++ .../cache/CacheAutoConfigurationTests.java | 178 ++++++++++++++--- spring-boot-dependencies/pom.xml | 18 ++ spring-boot-docs/src/main/asciidoc/index.adoc | 2 + .../main/asciidoc/spring-boot-features.adoc | 80 +++++++- .../spring-boot-sample-cache/README.adoc | 23 +++ .../spring-boot-sample-cache/pom.xml | 187 ++++++++++++------ .../cache/config/GeodeConfiguration.java | 40 ++++ .../src/main/resources/geode.xml | 26 +++ 12 files changed, 549 insertions(+), 96 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GeodeCacheConfiguration.java create mode 100644 spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/cache/config/GeodeConfiguration.java create mode 100644 spring-boot-samples/spring-boot-sample-cache/src/main/resources/geode.xml diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 93b509bfa5a0..f7b6921c83df 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -355,6 +355,11 @@ + + org.springframework.data + spring-data-geode + true + org.springframework.data spring-data-jpa 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 c9dc7b85e175..bd00c7ec1301 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 @@ -27,6 +27,7 @@ * * @author Phillip Webb * @author Eddú Meléndez + * @author John Blum */ final class CacheConfigurations { @@ -42,6 +43,7 @@ final class CacheConfigurations { mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class); mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class); + mappings.put(CacheType.GEODE, GeodeCacheConfiguration.class); mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class); mappings.put(CacheType.NONE, NoOpCacheConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); 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 6d29877e678b..9bbc911fbd7e 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 @@ -22,6 +22,7 @@ * @author Stephane Nicoll * @author Phillip Webb * @author Eddú Meléndez + * @author John Blum * @since 1.3.0 */ public enum CacheType { @@ -66,6 +67,11 @@ public enum CacheType { */ CAFFEINE, + /** + * Apache Geode backed caching. + */ + GEODE, + /** * Simple in-memory caching. */ @@ -74,6 +80,6 @@ public enum CacheType { /** * No caching. */ - NONE; + NONE } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GeodeCacheConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GeodeCacheConfiguration.java new file mode 100644 index 000000000000..4200f9bc7266 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/cache/GeodeCacheConfiguration.java @@ -0,0 +1,76 @@ +/* + * Copyright 2011-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.HashSet; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.client.ClientCache; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +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.data.gemfire.CacheFactoryBean; +import org.springframework.data.gemfire.client.ClientCacheFactoryBean; +import org.springframework.data.gemfire.support.GemfireCacheManager; + +/** + * GemFire cache configuration. + * + * @author John Blum + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.data.gemfire.support.GemfireCacheManager + * @since 1.5.0 + */ +@Configuration +@ConditionalOnBean({ CacheFactoryBean.class, Cache.class, + ClientCacheFactoryBean.class, ClientCache.class }) +@ConditionalOnClass(GemfireCacheManager.class) +@ConditionalOnMissingBean(CacheManager.class) +@Conditional(CacheCondition.class) +class GeodeCacheConfiguration { + + private final CacheProperties cacheProperties; + + private final CacheManagerCustomizers cacheManagerCustomizers; + + GeodeCacheConfiguration(CacheProperties cacheProperties, + CacheManagerCustomizers cacheManagerCustomizers) { + + this.cacheProperties = cacheProperties; + this.cacheManagerCustomizers = cacheManagerCustomizers; + } + + @Bean + public GemfireCacheManager cacheManager(GemFireCache gemfireCache) { + GemfireCacheManager cacheManager = new GemfireCacheManager(); + + cacheManager.setCache(gemfireCache); + cacheManager.setCacheNames(new HashSet(this.cacheProperties.getCacheNames())); + + return this.cacheManagerCustomizers.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 63b239a7a0ca..50caaeac506f 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 @@ -21,8 +21,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.cache.Caching; import javax.cache.configuration.CompleteConfiguration; @@ -40,6 +42,7 @@ import com.hazelcast.core.HazelcastInstance; import com.hazelcast.spring.cache.HazelcastCacheManager; import net.sf.ehcache.Status; +import org.apache.geode.cache.Region; import org.ehcache.jsr107.EhcacheCachingProvider; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.jcache.embedded.JCachingProvider; @@ -76,11 +79,13 @@ import org.springframework.context.annotation.Import; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.data.gemfire.support.GemfireCacheManager; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.core.RedisTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -90,6 +95,7 @@ * * @author Stephane Nicoll * @author Eddú Meléndez + * @author John Blum */ public class CacheAutoConfigurationTests { @@ -98,6 +104,12 @@ public class CacheAutoConfigurationTests { private AnnotationConfigApplicationContext context; + static Set asSet(T... elements) { + Set set = new HashSet(elements.length); + Collections.addAll(set, elements); + return set; + } + @After public void tearDown() { if (this.context != null) { @@ -161,13 +173,13 @@ public void simpleCacheExplicit() { @Test public void simpleCacheWithCustomizers() { testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "simple", - "allCacheManagerCustomizer", "simpleCacheManagerCustomizer"); + "allCacheManagerCustomizer", "simpleCacheManagerCustomizer"); } @Test public void simpleCacheExplicitWithCacheNames() { load(DefaultCacheConfiguration.class, "spring.cache.type=simple", - "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); + "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); ConcurrentMapCacheManager cacheManager = validateCacheManager( ConcurrentMapCacheManager.class); assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar"); @@ -195,7 +207,7 @@ public void genericCacheExplicit() { @Test public void genericCacheWithCustomizers() { testCustomizers(GenericCacheAndCustomizersConfiguration.class, "generic", - "allCacheManagerCustomizer", "genericCacheManagerCustomizer"); + "allCacheManagerCustomizer", "genericCacheManagerCustomizer"); } @Test @@ -220,7 +232,7 @@ public void couchbaseCacheExplicit() { @Test public void couchbaseCacheWithCustomizers() { testCustomizers(CouchbaseCacheAndCustomizersConfiguration.class, "couchbase", - "allCacheManagerCustomizer", "couchbaseCacheManagerCustomizer"); + "allCacheManagerCustomizer", "couchbaseCacheManagerCustomizer"); } @Test @@ -252,6 +264,47 @@ public void couchbaseCacheExplicitWithTtl() { .isEqualTo(this.context.getBean("bucket")); } + @Test + public void geodeCacheExplicit() { + load(GeodeCacheConfiguration.class, "spring.cache.type=geode"); + GemfireCacheManager cacheManager = validateCacheManager(GemfireCacheManager.class); + assertThat(cacheManager.getCacheNames()).isEmpty(); + } + + @Test + public void geodeCacheExplicitWithCaches() { + load(GeodeCacheRegionsConfiguration.class, "spring.cache.type=geode"); + GemfireCacheManager cacheManager = validateCacheManager(GemfireCacheManager.class); + assertThat(cacheManager.getCacheNames()).isEqualTo(asSet("one", "two")); + } + + @Test + public void geodeCacheExplicitWithNamedCaches() { + load(GeodeCacheRegionsConfiguration.class, "spring.cache.type=geode", "spring.cache.cache-names=one"); + GemfireCacheManager cacheManager = validateCacheManager(GemfireCacheManager.class); + assertThat(cacheManager.getCacheNames()).isEqualTo(asSet("one")); + } + + @Test + public void geodeCacheWithCustomizers() { + testCustomizers(GeodeCacheCustomizersConfiguration.class, "geode", + "allCacheManagerCustomizer", "geodeCacheManagerCustomizer"); + } + + @Test + public void geodeCacheWithGenericCacheManager() { + load(GeodeCacheGenericConfiguration.class); + ConcurrentMapCacheManager cacheManager = validateCacheManager(ConcurrentMapCacheManager.class); + assertThat(cacheManager.getCacheNames()).isEqualTo(asSet("example")); + } + + @Test + public void geodeCacheWithRedisConfiguration() { + load(GeodeCacheRedisConfiguration.class, "spring.cache.type=redis"); + RedisCacheManager cacheManager = validateCacheManager(RedisCacheManager.class); + assertThat(cacheManager.getCacheNames()).isEmpty(); + } + @Test public void redisCacheExplicit() { load(RedisCacheConfiguration.class, "spring.cache.type=redis"); @@ -264,7 +317,7 @@ public void redisCacheExplicit() { @Test public void redisCacheWithCustomizers() { testCustomizers(RedisCacheAndCustomizersConfiguration.class, "redis", - "allCacheManagerCustomizer", "redisCacheManagerCustomizer"); + "allCacheManagerCustomizer", "redisCacheManagerCustomizer"); } @Test @@ -294,7 +347,7 @@ public void jCacheCacheNoProviderExplicit() { public void jCacheCacheWithProvider() { String cachingProviderFqn = MockCachingProvider.class.getName(); load(DefaultCacheConfiguration.class, "spring.cache.type=jcache", - "spring.cache.jcache.provider=" + cachingProviderFqn); + "spring.cache.jcache.provider=" + cachingProviderFqn); JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); assertThat(cacheManager.getCacheNames()).isEmpty(); assertThat(this.context.getBean(javax.cache.CacheManager.class)) @@ -315,16 +368,16 @@ public void jCacheCacheWithCaches() { public void jCacheCacheWithCachesAndCustomConfig() { String cachingProviderFqn = MockCachingProvider.class.getName(); load(JCacheCustomConfiguration.class, "spring.cache.type=jcache", - "spring.cache.jcache.provider=" + cachingProviderFqn, - "spring.cache.cacheNames[0]=one", "spring.cache.cacheNames[1]=two"); + "spring.cache.jcache.provider=" + cachingProviderFqn, + "spring.cache.cacheNames[0]=one", "spring.cache.cacheNames[1]=two"); JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); assertThat(cacheManager.getCacheNames()).containsOnly("one", "two"); CompleteConfiguration defaultCacheConfiguration = this.context .getBean(CompleteConfiguration.class); verify(cacheManager.getCacheManager()).createCache("one", - defaultCacheConfiguration); + defaultCacheConfiguration); verify(cacheManager.getCacheManager()).createCache("two", - defaultCacheConfiguration); + defaultCacheConfiguration); } @Test @@ -341,7 +394,7 @@ public void jCacheCacheWithUnknownProvider() { this.thrown.expect(BeanCreationException.class); this.thrown.expectMessage(wrongCachingProviderFqn); load(DefaultCacheConfiguration.class, "spring.cache.type=jcache", - "spring.cache.jcache.provider=" + wrongCachingProviderFqn); + "spring.cache.jcache.provider=" + wrongCachingProviderFqn); } @Test @@ -349,8 +402,8 @@ public void jCacheCacheWithConfig() throws IOException { String cachingProviderFqn = MockCachingProvider.class.getName(); String configLocation = "org/springframework/boot/autoconfigure/cache/hazelcast-specific.xml"; load(JCacheCustomConfiguration.class, "spring.cache.type=jcache", - "spring.cache.jcache.provider=" + cachingProviderFqn, - "spring.cache.jcache.config=" + configLocation); + "spring.cache.jcache.provider=" + cachingProviderFqn, + "spring.cache.jcache.config=" + configLocation); JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); Resource configResource = new ClassPathResource(configLocation); assertThat(cacheManager.getCacheManager().getURI()) @@ -365,8 +418,8 @@ public void jCacheCacheWithWrongConfig() { this.thrown.expectMessage("does not exist"); this.thrown.expectMessage(configLocation); load(JCacheCustomConfiguration.class, "spring.cache.type=jcache", - "spring.cache.jcache.provider=" + cachingProviderFqn, - "spring.cache.jcache.config=" + configLocation); + "spring.cache.jcache.provider=" + cachingProviderFqn, + "spring.cache.jcache.config=" + configLocation); } @Test @@ -382,17 +435,17 @@ public void ehcacheCacheWithCaches() { @Test public void ehcacheCacheWithCustomizers() { testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "ehcache", - "allCacheManagerCustomizer", "ehcacheCacheManagerCustomizer"); + "allCacheManagerCustomizer", "ehcacheCacheManagerCustomizer"); } @Test public void ehcacheCacheWithConfig() { load(DefaultCacheConfiguration.class, "spring.cache.type=ehcache", - "spring.cache.ehcache.config=cache/ehcache-override.xml"); + "spring.cache.ehcache.config=cache/ehcache-override.xml"); EhCacheCacheManager cacheManager = validateCacheManager( EhCacheCacheManager.class); assertThat(cacheManager.getCacheNames()).containsOnly("cacheOverrideTest1", - "cacheOverrideTest2"); + "cacheOverrideTest2"); } @Test @@ -408,8 +461,8 @@ public void ehcacheCacheWithExistingCacheManager() { public void ehcache3AsJCacheWithCaches() { String cachingProviderFqn = EhcacheCachingProvider.class.getName(); load(DefaultCacheConfiguration.class, "spring.cache.type=jcache", - "spring.cache.jcache.provider=" + cachingProviderFqn, - "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); + "spring.cache.jcache.provider=" + cachingProviderFqn, + "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); assertThat(cacheManager.getCacheNames()).containsOnly("foo", "bar"); } @@ -444,7 +497,7 @@ public void hazelcastCacheExplicit() { @Test public void hazelcastCacheWithCustomizers() { testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "hazelcast", - "allCacheManagerCustomizer", "hazelcastCacheManagerCustomizer"); + "allCacheManagerCustomizer", "hazelcastCacheManagerCustomizer"); } @Test @@ -459,8 +512,8 @@ public void hazelcastCacheWithConfig() throws IOException { assertThat(actual).isSameAs(hazelcastInstance); assertThat(actual.getConfig().getConfigurationUrl()) .isEqualTo(new ClassPathResource( - "org/springframework/boot/autoconfigure/cache/hazelcast-specific.xml") - .getURL()); + "org/springframework/boot/autoconfigure/cache/hazelcast-specific.xml") + .getURL()); cacheManager.getCache("foobar"); assertThat(cacheManager.getCacheNames()).containsOnly("foobar"); } @@ -549,8 +602,8 @@ public void hazelcastAsJCacheWithConfig() throws IOException { String cachingProviderFqn = HazelcastCachingProvider.class.getName(); String configLocation = "org/springframework/boot/autoconfigure/cache/hazelcast-specific.xml"; load(DefaultCacheConfiguration.class, "spring.cache.type=jcache", - "spring.cache.jcache.provider=" + cachingProviderFqn, - "spring.cache.jcache.config=" + configLocation); + "spring.cache.jcache.provider=" + cachingProviderFqn, + "spring.cache.jcache.config=" + configLocation); JCacheCacheManager cacheManager = validateCacheManager(JCacheCacheManager.class); Resource configResource = new ClassPathResource(configLocation); @@ -570,7 +623,7 @@ public void infinispanCacheWithConfig() { @Test public void infinispanCacheWithCustomizers() { testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "infinispan", - "allCacheManagerCustomizer", "infinispanCacheManagerCustomizer"); + "allCacheManagerCustomizer", "infinispanCacheManagerCustomizer"); } @Test @@ -646,7 +699,7 @@ public void caffeineCacheWithExplicitCaches() { @Test public void caffeineCacheWithCustomizers() { testCustomizers(DefaultCacheAndCustomizersConfiguration.class, "caffeine", - "allCacheManagerCustomizer", "caffeineCacheManagerCustomizer"); + "allCacheManagerCustomizer", "caffeineCacheManagerCustomizer"); } @Test @@ -666,8 +719,8 @@ public void caffeineCacheExplicitWithSpec() { @Test public void caffeineCacheExplicitWithSpecString() { load(DefaultCacheConfiguration.class, "spring.cache.type=caffeine", - "spring.cache.caffeine.spec=recordStats", - "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); + "spring.cache.caffeine.spec=recordStats", + "spring.cache.cacheNames[0]=foo", "spring.cache.cacheNames[1]=bar"); validateCaffeineCacheWithStats(); } @@ -784,6 +837,67 @@ static class CouchbaseCacheAndCustomizersConfiguration { } + @Configuration + @EnableCaching + static class GeodeCacheConfiguration { + + @Bean + public org.apache.geode.cache.Cache gemfireCache() { + org.apache.geode.cache.Cache mockCache = mock(org.apache.geode.cache.Cache.class); + given(mockCache.rootRegions()).willReturn(Collections.>emptySet()); + return mockCache; + } + } + + @Configuration + @Import({ GeodeCacheConfiguration.class, CacheManagerCustomizersConfiguration.class }) + static class GeodeCacheCustomizersConfiguration { + + } + + @Configuration + @EnableCaching + static class GeodeCacheRegionsConfiguration { + + @Bean + public org.apache.geode.cache.Cache gemfireCache() { + org.apache.geode.cache.Cache mockCache = mock(org.apache.geode.cache.Cache.class); + Set> mockRegions = asSet(mockRegion(mockCache, "one"), mockRegion(mockCache, "two")); + given(mockCache.rootRegions()).willReturn(mockRegions); + return mockCache; + } + + @SuppressWarnings("unchecked") + private Region mockRegion(org.apache.geode.cache.Cache mockCache, String name) { + Region mockRegion = mock(Region.class); + given(mockRegion.getName()).willReturn(name); + given(mockCache.getRegion(eq(name))).willReturn(mockRegion); + return mockRegion; + } + } + + @Configuration + @EnableCaching + @Import(GeodeCacheConfiguration.class) + static class GeodeCacheGenericConfiguration { + + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager("example"); + } + } + + @Configuration + @EnableCaching + @Import(GeodeCacheRegionsConfiguration.class) + static class GeodeCacheRedisConfiguration { + + @Bean + public RedisTemplate redisTemplate() { + return mock(RedisTemplate.class); + } + } + @Configuration @EnableCaching static class RedisCacheConfiguration { @@ -1031,6 +1145,12 @@ public CacheManagerCustomizer caffeineCacheManagerCustomiz }; } + @Bean + public CacheManagerCustomizer geodeCacheManagerCustomizer() { + return new CacheManagerTestCustomizer() { + }; + } + } static abstract class CacheManagerTestCustomizer diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 63594cb213ac..180fcef430db 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -73,6 +73,7 @@ 2.3.25-incubating 2.4.1 8.2.0 + 1.0.0-incubating 3.0.0 2.9 2.4.7 @@ -147,6 +148,7 @@ 1.6.4.BUILD-SNAPSHOT 1.2.3.RELEASE 3.0.7.RELEASE + 1.0.0.INCUBATING-RELEASE Ingalls-M1 0.21.0.RELEASE 5.0.0.BUILD-SNAPSHOT @@ -1189,6 +1191,17 @@ derby ${derby.version} + + org.apache.geode + geode-core + ${geode.version} + + + org.apache.shiro + shiro-core + + + org.apache.httpcomponents httpasyncclient @@ -2002,6 +2015,11 @@ + + org.springframework.data + spring-data-geode + ${spring-data-geode.version} + org.springframework.data spring-data-releasetrain diff --git a/spring-boot-docs/src/main/asciidoc/index.adoc b/spring-boot-docs/src/main/asciidoc/index.adoc index a60242f89b40..56708b8f440d 100644 --- a/spring-boot-docs/src/main/asciidoc/index.adoc +++ b/spring-boot-docs/src/main/asciidoc/index.adoc @@ -43,6 +43,7 @@ Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; Andy Wilkinson; :spring-amqp-javadoc: http://docs.spring.io/spring-amqp/docs/current/api/org/springframework/amqp :spring-data-javadoc: http://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa :spring-data-commons-javadoc: http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data +:spring-data-geode-reference: http://docs.spring.io/spring-data-gemfire/docs/current/reference/html :spring-data-mongo-javadoc: http://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb :spring-data-rest-javadoc: http://docs.spring.io/spring-data/rest/docs/current/api/org/springframework/data/rest :gradle-userguide: http://www.gradle.org/docs/current/userguide @@ -51,6 +52,7 @@ Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; Andy Wilkinson; :code-examples: ../java/org/springframework/boot :gradle-user-guide: https://docs.gradle.org/2.14.1/userguide :gradle-dsl: https://docs.gradle.org/2.14.1/dsl +:apache-geode-user-docs: http://geode.docs.pivotal.io/ // ====================================================================================== include::documentation-overview.adoc[] 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 a0a9a7b7df77..d6869c647403 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3737,7 +3737,7 @@ will replace the default. [[boot-features-caching]] == Caching The Spring Framework provides support for transparently adding caching to an application. -At its core, the abstraction applies caching to methods, reducing thus the number of +At its core, the abstraction applies caching to methods, thus reducing the number of executions based on the information available in the cache. The caching logic is applied transparently, without any interference to the invoker. @@ -3765,7 +3765,7 @@ relevant annotation to its method: ---- NOTE: You can either use the standard JSR-107 (JCache) annotations or Spring's own -caching annotations transparently. We strongly advise you however to not mix and match +caching annotations transparently. However, we strongly advise you to not mix and match them. TIP: It is also possible to {spring-reference}/#cache-annotations-put[update] or @@ -3801,6 +3801,7 @@ providers (in this order): * <> * <> * <> +* <> * <> TIP: It is also possible to _force_ the cache provider to use via the `spring.cache.type` @@ -4022,6 +4023,81 @@ as `CacheLoader`. Any other generic type will be ignored by the auto-configuration. +[[boot-features-caching-provider-geode]] +==== Geode +When _Apache Geode_ is present and a Geode `Cache` or `ClientCache` bean has been +properly configured, Spring Boot will auto-configure the `GemfireCacheManager`. + +By default, any configured Geode `Region` will function as a Spring cache. The only +requirement is that the name of the cache identified in a caching annotation match +the name of a configured Geode `Region`. However, it is possible to identify specific +`Regions` by name using the `spring.cache.cache-names` property that will purposefully +function as Spring caches on startup. + +NOTE: Unlike some other stores, specifying the `spring.cache.cache-names` does not +create the Geode `Region` in which application data will be cached. You must explicitly +configure and create the `Region` yourself using either Geode's native configuration +meta-data or _Spring Data Geode_. + +To enable caching in your application with Geode as the provider, it is as simple as +adding the _Spring Data Geode_ dependency to your application classpath and creating +either a Geode peer `Cache` bean or `ClientCache` bean along with the `Regions` that +will function as Spring caches in your application. + +[source,java,indent=0] +---- +@Configuration +public class PeerCacheGeodeConfiguration { + + @Bean + public CacheFactoryBean gemfireCache() { + CacheFactoryBean gemfireCache = new CacheFactoryBean(); + gemfireCache.setClose(true); + return gemfireCache; + } + + @Bean + public PartitionedRegionFactoryBean computePiDecimal() { + PartitionedRegionFactoryBean regionFactory = new PartitionedRegionFactoryBean(); + regionFactory.setCache(gemfireCache()); + regionFactory.setClose(false); + return regionFactory; + } +} +---- + +Or, if you are using Geode's client/server topology... + +[source,java,indent=0] +---- +@Configuration +public class ClientCacheGeodeConfiguration { + + @Bean + public ClientCacheFactoryBean gemfireCache() { + ClientCacheFactoryBean gemfireCache = new ClientCacheFactoryBean(); + gemfireCache.setClose(true); + gemfireCache.setServers(new ConnectionEndpoint("host", 40404)); + return gemfireCache; + } + + @Bean + public ClientRegionFactoryBean computePiDecimal() { + ClientRegionFactoryBean regionFactory = new ClientRegionFactoryBean(); + regionFactory.setCache(gemfireCache()); + regionFactory.setClose(false); + regionFactory.setShortcut(ClientRegionShortcut.PROXY); + return regionFactory; + } +} +---- + +Refer to _Spring Data Geode's_ {spring-data-geode-reference}[Reference Guide] on how to +configure _Apache Geode_ caches and `Regions` as well as for additional options on +{spring-data-geode-reference}/#apis:spring-cache-abstraction[configuring] _Apache Geode_ +as a caching provider. To learn more about _Apache Geode_ refer to the +{apache-geode-user-docs}[user documentation]. + [[boot-features-caching-provider-simple]] ==== Simple diff --git a/spring-boot-samples/spring-boot-sample-cache/README.adoc b/spring-boot-samples/spring-boot-sample-cache/README.adoc index 0adc8653c587..c1e9df0f150c 100644 --- a/spring-boot-samples/spring-boot-sample-cache/README.adoc +++ b/spring-boot-samples/spring-boot-sample-cache/README.adoc @@ -10,6 +10,7 @@ abstraction is supported by many caching libraries, including: * `Couchbase` * `Redis` * `Caffeine` +* `Geode` * Simple provider based on `ConcurrentHashMap` * Generic provider based on `org.springframework.Cache` bean definition(s) @@ -63,6 +64,8 @@ used to configure the underlying `CacheManager`. Note that EhCache 3 uses a diff format and doesn't default to `ehcache.xml` anymore. Check http://www.ehcache.org/documentation/3.0/xml.html[the documentation] for more details. +Run sample cache application using EhCache with `$mvn -P ehcache spring-boot:run` + === Hazelcast @@ -71,6 +74,8 @@ the project to enable support for Hazelcast. Since there is a default `hazelcas configuration file at the root of the classpath, it is used to automatically configure the underlying `HazelcastInstance`. +Run sample cache application using Hazelcast with `$mvn -P hazelcast spring-boot:run` + === Infinispan @@ -80,21 +85,39 @@ so if you don't specify anything it will bootstrap on a hardcoded default. You c the `spring.cache.infinispan.config` property to use the provided `infinispan.xml` configuration instead. +Run sample cache application using Infinispan with `$mvn -P infinispan spring-boot:run` + === 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. +Start a Couchbase server, then run the sample cache application using Couchbase with `$mvn -P couchbase spring-boot:run` + === 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). +Start a Redis server, then run the sample cache application using Redis with `$mvn -P redis spring-boot:run` + === Caffeine Simply add the `com.github.ben-manes.caffeine:caffeine` dependency to enable support for Caffeine. You can customize how caches are created in different ways, see `application.properties` for an example and the documentation for more details. + +Run sample cache application using Caffeine with `$mvn -P caffeine spring-boot:run` + + + +=== Geode +Add the `spring-boot-starter-data-gemfire` dependency and make sure your Geode instance +is configured properly. This involves creating an instance of either a Geode peer `Cache` +or `ClientCache` depending on your topology preference and additionally creating +and configuring Regions that will serve as the caches for your application. + +Run sample cache application using Geode with `$mvn -P geode spring-boot:run` diff --git a/spring-boot-samples/spring-boot-sample-cache/pom.xml b/spring-boot-samples/spring-boot-sample-cache/pom.xml index c9981bb7176c..6d60408b5e9a 100644 --- a/spring-boot-samples/spring-boot-sample-cache/pom.xml +++ b/spring-boot-samples/spring-boot-sample-cache/pom.xml @@ -32,70 +32,6 @@ org.springframework.boot spring-boot-starter-actuator - - - - - - - - - - - - org.springframework.boot spring-boot-starter-test @@ -110,4 +46,127 @@ + + + + + jcache + + + javax.cache + cache-api + + + + + + caffeine + + + com.github.ben-manes.caffeine + caffeine + + + + + + + couchbase + + + com.couchbase.client + java-client + + + com.couchbase.client + couchbase-spring-cache + + + + + + ehcache + + + net.sf.ehcache + ehcache + + + + + + + org-ehcache + + + org.ehcache + ehcache + + + + + + geode + + + + org.springframework.data + spring-data-geode + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + geode + + + + + + + + + hazelcast + + + com.hazelcast + hazelcast + + + com.hazelcast + hazelcast-spring + + + + + + infinispan + + + org.infinispan + infinispan-spring4-embedded + + + org.infinispan + infinispan-jcache + + + + + + + redis + + + org.springframework.boot + spring-boot-starter-data-redis + + + + diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/cache/config/GeodeConfiguration.java b/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/cache/config/GeodeConfiguration.java new file mode 100644 index 000000000000..87e0ec55c78c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/java/sample/cache/config/GeodeConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2011-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 sample.cache.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; +import org.springframework.context.annotation.Profile; + +/** + * Spring {@link Configuration @Configuration} class to configure Geode as a caching provider. + * This {@link Configuration @Configuration} class is conditionally loaded based on + * whether Geode is on the classpath. + * + * @author John Blum + * @see org.springframework.boot.autoconfigure.condition.ConditionalOnClass + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.ImportResource + * @since 1.5.0 + */ +@Configuration +@ImportResource("geode.xml") +@Profile("geode") +@SuppressWarnings("unused") +public class GeodeConfiguration { + +} diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/geode.xml b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/geode.xml new file mode 100644 index 000000000000..2d3d5f9ba29c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/geode.xml @@ -0,0 +1,26 @@ + + + + + + + SampleGeodeCacheApplication + 0 + ${sample.cache.geode.log.level:config} + + + + + + +