diff --git a/build.gradle b/build.gradle index 8ab14bbc..668284c5 100644 --- a/build.gradle +++ b/build.gradle @@ -4,9 +4,11 @@ buildscript { } dependencies { + classpath 'io.spring.gradle:docbook-reference-plugin:0.3.0' + classpath 'io.spring.gradle:spring-io-plugin:0.0.4.RELEASE' classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.0' classpath 'org.asciidoctor:asciidoctorj:1.5.0' - classpath 'org.springframework.build.gradle:bundlor-plugin:0.1.2' + classpath 'org.springframework.build.gradle:bundlor-plugin:0.1.3' classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.6' classpath 'io.spring.gradle:docbook-reference-plugin:0.3.0' classpath 'io.spring.gradle:spring-io-plugin:0.0.4.RELEASE' @@ -33,6 +35,17 @@ archivesBaseName = project.name sourceCompatibility = 1.6 targetCompatibility = 1.6 +sourceSets { + main { + output.classesDir = 'build/classes/main' + output.resourcesDir = 'build/classes/main' + } + test { + output.classesDir = 'build/classes/test' + output.resourcesDir = 'build/classes/test' + } +} + [compileJava, compileTestJava]*.options*.compilerArgs = ["-Xlint:-serial"] if (project.hasProperty('platformVersion')) { @@ -68,17 +81,6 @@ tasks.withType(Test).all { systemProperties['spring.profiles.active'] = 'apache-geode' } -sourceSets { - main { - output.resourcesDir = 'build/classes/main' - output.classesDir = 'build/classes/main' - } - test { - output.resourcesDir = 'build/classes/test' - output.classesDir = 'build/classes/test' - } -} - repositories { maven { url "https://repo.spring.io/libs-release" } maven { url "https://repo.spring.io/plugins-release"} @@ -121,13 +123,13 @@ dependencies { testCompile("org.springframework:spring-test:$springVersion") { exclude group: "commons-logging", module: "commons-logging" } - testCompile "org.assertj:assertj-core:$assertjVersion" testCompile "junit:junit:$junitVersion" + testCompile "org.apache.openwebbeans.test:cditest-owb:$openwebbeansVersion" + testCompile "org.assertj:assertj-core:$assertjVersion" testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion" testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion" testCompile "org.mockito:mockito-core:$mockitoVersion" testCompile "edu.umd.cs.mtc:multithreadedtc:$multiThreadedtcVersion" - testCompile "org.apache.openwebbeans.test:cditest-owb:$openwebbeansVersion" testRuntime "javax.el:el-api:$cdiVersion" testRuntime "javax.servlet:servlet-api:$servletApiVersion" @@ -159,33 +161,42 @@ jar { } } -javadoc { - dependsOn = [ 'extractSharedResources' ] - destinationDir = file("${buildDir}/api") - ext.srcDir = file("$buildDir/shared-resources") - ext.tmpDir = file("${buildDir}/api-work") - - configure(options) { - stylesheetFile = file("$buildDir/shared-resources/javadoc/spring-javadoc.css") - docFilesSubDirs = true - outputLevel = org.gradle.external.javadoc.JavadocOutputLevel.QUIET - breakIterator = true - showFromProtected() - groups = [ - 'Spring GemFire' : ['org.springframework.data.gemfire*'], - ] - - links = [ - "http://docs.oracle.com/javase/6/docs/api/", - "http://docs.spring.io/spring/docs/current/javadoc-api/", - "http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/", - "http://logging.apache.org/log4j/1.2/apidocs/", - ] - - exclude "org/springframework/data/gemfire/config/**" +task extractSharedResources(type: Copy) { + from { + // use of closure defers evaluation until execution time + configurations.sharedResources.collect { zipTree(it) } } + into "$buildDir/shared-resources" +} - title = "${rootProject.description} ${version} API" +javadoc { + dependsOn = [ 'extractSharedResources' ] + + destinationDir = file("${buildDir}/api") + ext.tmpDir = file("${buildDir}/api-work") + + configure(options) { + breakIterator = true + docFilesSubDirs = true + outputLevel = org.gradle.external.javadoc.JavadocOutputLevel.QUIET + stylesheetFile = file("$buildDir/shared-resources/javadoc/spring-javadoc.css") + showFromProtected() + + groups = [ + 'Spring GemFire' : ['org.springframework.data.gemfire*'], + ] + + links = [ + "http://docs.oracle.com/javase/6/docs/api/", + "http://docs.spring.io/spring/docs/current/javadoc-api/", + "http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/", + "http://logging.apache.org/log4j/1.2/apidocs/", + ] + + exclude "org/springframework/data/gemfire/config/**" + } + + title = "${rootProject.description} ${version} API" } asciidoctor { @@ -233,6 +244,31 @@ task javadocJar(type: Jar, dependsOn: javadoc) { from javadoc } +task docsZip(type: Zip) { + group = 'Distribution' + classifier = 'docs' + description = "Builds -${classifier} archive containing api and reference for deployment" + dependsOn = [ 'javadoc', 'asciidoctor', 'referencePdf' ] + + from('docs/src/info') { + include 'changelog.txt' + } + + from (javadoc) { + into 'api' + } + + from (asciidoctor.outputDir) { + include "index.html" + include "spring.css" + into 'reference/html' + } + + from (referencePdf.outputDir) { + into 'reference' + } +} + task schemaZip(type: Zip) { description = "Builds -${classifier} archive containing all XSDs for deployment" baseName = project.archivesBaseName @@ -264,32 +300,7 @@ task schemaZip(type: Zip) { } } -task docsZip(type: Zip, dependsOn: [javadoc, asciidoctor, referencePdf]) { - description = "Builds -${classifier} archive containing api and reference for deployment" - baseName = project.archivesBaseName - classifier = 'docs' - group = 'Distribution' - - from('src/main/resources') { - include 'changelog.txt' - } - - from (javadoc) { - into 'api' - } - - from (asciidoctor.outputDir) { - include "index.html" - include "spring.css" - into 'reference/html' - } - - from (referencePdf.outputDir) { - into 'reference' - } -} - -task distZip(type: Zip, dependsOn: [jar, sourcesJar, javadocJar, schemaZip, docsZip]) { +task distZip(type: Zip, dependsOn: [jar, docsZip, schemaZip, sourcesJar, javadocJar]) { description = "Builds -${classifier} archive, containing all jars and docs, suitable for community download page." baseName = project.archivesBaseName @@ -300,6 +311,7 @@ task distZip(type: Zip, dependsOn: [jar, sourcesJar, javadocJar, schemaZip, docs into (zipRootDir) { from('src/main/resources') { + include 'changelog.txt' include 'license.txt' include 'notice.txt' include 'readme.txt' @@ -328,14 +340,6 @@ task distZip(type: Zip, dependsOn: [jar, sourcesJar, javadocJar, schemaZip, docs } } -task extractSharedResources(type: Copy) { - from { // use of closure defers evaluation until execution time - configurations.sharedResources.collect { zipTree(it) } - } - - into "$buildDir/shared-resources" -} - artifacts { archives sourcesJar archives javadocJar diff --git a/gradle.properties b/gradle.properties index 3835875f..df131faf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ antlrVersion=2.7.7 aspectjVersion=1.8.9 -assertjVersion=3.5.2 +assertjVersion=2.5.0 cdiVersion=1.0 gemfireVersion=1.0.0-incubating.M3 googleCodeFindbugsVersion=2.0.2 @@ -13,8 +13,8 @@ multiThreadedtcVersion=1.01 openwebbeansVersion=1.2.8 servletApiVersion=2.5 slf4jVersion=1.7.21 -springVersion=4.2.8.RELEASE spring.range="[4.0.0, 5.0.0)" +springVersion=4.3.3.RELEASE springDataBuildVersion=1.8.3.RELEASE springDataCommonsVersion=1.12.3.RELEASE springShellVersion=1.1.0.RELEASE diff --git a/pom.xml b/pom.xml index e0f9f709..e439d205 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.springframework.data.build spring-data-parent - 1.8.3.RELEASE + 1.9.0.BUILD-SNAPSHOT org.springframework.data @@ -20,7 +20,7 @@ SGF 2.7.7 - 3.5.2 + 2.5.0 1.0.0-incubating.M3 2.0.2 1.01 @@ -169,16 +169,16 @@ - org.apache.openwebbeans.test - cditest-owb - ${webbeans} + org.apache.derby + derbyLocale_zh_TW + 10.9.1.0 test - org.apache.derby - derbyLocale_zh_TW - 10.9.1.0 + org.apache.openwebbeans.test + cditest-owb + ${webbeans} test diff --git a/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java b/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java index d8bb1478..5287387a 100644 --- a/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java +++ b/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java @@ -18,18 +18,16 @@ package org.springframework.data.gemfire.support; import java.util.concurrent.Callable; -import org.springframework.cache.Cache; -import org.springframework.cache.support.SimpleValueWrapper; -import org.springframework.util.ObjectUtils; - import com.gemstone.gemfire.cache.GemFireCache; import com.gemstone.gemfire.cache.Region; +import org.springframework.cache.Cache; +import org.springframework.cache.support.SimpleValueWrapper; +import org.springframework.util.Assert; + /** * Spring Framework {@link Cache} implementation backed by a GemFire {@link Region}. * - * Supports GemFire 6.5 or higher. - * * @author Costin Leau * @author John Blum * @author Oliver Gierke @@ -38,58 +36,133 @@ import com.gemstone.gemfire.cache.Region; */ public class GemfireCache implements Cache { - @SuppressWarnings({ "rawtypes" }) private final Region region; /** - * Creates a {@link GemFireCache} instance. - * - * @param region backing GemFire region + * Wraps a GemFire {@link Region} in an instance of {@link GemfireCache} to adapt the GemFire {@link Region} + * to function as a Spring {@link Cache} in Spring's caching infrastructure. + * + * @param region GemFire {@link Region} to wrap. + * @return an instance of {@link GemfireCache} backed by the provided GemFire {@link Region}. + * @see com.gemstone.gemfire.cache.Region + * @see org.springframework.cache.Cache + * @see #GemfireCache(Region) */ - public GemfireCache(final Region region) { + public static GemfireCache wrap(Region region) { + return new GemfireCache(region); + } + + /** + * Constructs an instance of {@link GemFireCache} initialized with the given GemFire {@link Region}. + * The {@link Region} will function as the backing store and implementation for + * the Spring {@link Cache} interface. + * + * @param region GemFire {@link Region} backing the Spring {@link Cache}. + * @throws IllegalArgumentException if {@link Region} is null. + */ + public GemfireCache(Region region) { + Assert.notNull(region, "GemFire Region must not be null"); this.region = region; } + /** + * Returns the GemFire {@link Region} used as the implementation for this Spring {@link Cache}. + * + * @return the GemFire {@link Region} used as the implementation for this Spring {@link Cache}. + * @see com.gemstone.gemfire.cache.Region + */ + public Region getNativeCache() { + return this.region; + } + + /** + * Returns the name of this Spring {@link Cache}. + * + * @return the name of this Spring {@link Cache}. + * @see com.gemstone.gemfire.cache.Region#getName() + */ public String getName() { - return region.getName(); - } - - public Region getNativeCache() { - return region; + return getNativeCache().getName(); } + /** + * Clears the entire contents of this Spring {@link Cache}. + * + * @see com.gemstone.gemfire.cache.Region#clear() + */ public void clear() { - region.clear(); + getNativeCache().clear(); } - public void evict(final Object key) { - region.destroy(key); + /** + * Evicts (destroys) the entry (key/value) mapped to the given key from this Spring {@link Cache}. + * + * @param key key used to identify the cache entry to evict. + * @see com.gemstone.gemfire.cache.Region#destroy(Object) + */ + public void evict(Object key) { + getNativeCache().destroy(key); } - public ValueWrapper get(final Object key) { - Object value = region.get(key); + /** + * Returns the cache value for the given key wrapped in an instance of + * {@link org.springframework.cache.Cache.ValueWrapper}. + * + * @param key key identifying the the value to retrieve from the cache. + * @return the value cached with the given key. + * @see org.springframework.cache.Cache.ValueWrapper + * @see com.gemstone.gemfire.cache.Region#get(Object) + */ + public ValueWrapper get(Object key) { + Object value = getNativeCache().get(key); - return (value == null ? null : new SimpleValueWrapper(value)); + return (value != null ? new SimpleValueWrapper(value) : null); } + /** + * Returns the cache value for the given key cast to the specified {@link Class} type. + * + * @param desired {@link Class} type of the cache value. + * @param key key identifying the the value to retrieve from the cache. + * @param type desired {@link Class} type of the value. + * @return the cache value for the given key cast to the specified {@link Class} type. + * @throws IllegalStateException if the value is not null and not an instance of the desired type. + * @see com.gemstone.gemfire.cache.Region#get(Object) + */ @SuppressWarnings("unchecked") - public T get(final Object key, final Class type) { - Object value = region.get(key); + public T get(Object key, Class type) { + Object value = getNativeCache().get(key); if (value != null && type != null && !type.isInstance(value)) { - throw new IllegalStateException(String.format("Cached value is not of required type [%1$s]: %2$s", - type.getName(), value)); + throw new IllegalStateException(String.format( + "Cached value [%1$s] is not an instance of type [%2$s]", + value, type.getName())); } return (T) value; } + /** + * Returns the cache value for given key. If the value is {@literal null}, then the provided + * {@link Callable} {@code valueLoader} will be called to obtain a value and add the entry + * to this cache. + * + * @param {@link Class} type of the value. + * @param key key identifying the the value to retrieve from the cache. + * @param valueLoader {@link Callable} object used to load a value if the entry identified by the key + * does not already have value. + * @return the cache value of the given key or a value obtained by calling the {@link Callable} object + * if the value for key is {@literal null}. + * @throws org.springframework.cache.Cache.ValueRetrievalException if an error occurs while trying to + * load a value for given key using the {@link Callable}. + * @see #get(Object, Class) + */ @SuppressWarnings("unchecked") - public T get(final Object key, final Callable valueLoader) { + public T get(Object key, Callable valueLoader) { T value = (T) get(key, Object.class); if (value == null) { - synchronized (region) { + synchronized (getNativeCache()) { value = (T) get(key, Object.class); if (value == null) { @@ -98,11 +171,7 @@ public class GemfireCache implements Cache { put(key, value); } catch (Exception e) { - throw new RuntimeException(String.format( - "Failed to load value for key [%1$s] using valueLoader [%2$s]", key, - ObjectUtils.nullSafeClassName(valueLoader))); - //TODO throw ValueRetrievalException when SDG is based on Spring Framework 4.3 - //throw new ValueRetrievalException(key, valueLoader, e); + throw new ValueRetrievalException(key, valueLoader, e); } } } @@ -111,24 +180,34 @@ public class GemfireCache implements Cache { return value; } + /** + * Stores the given value in the cache referenced by the given key. This operation will only store the value + * if the value is not {@literal null}. + * + * @param key key used to reference the value in the cache. + * @param value value to store in the cache referenced by the key. + * @see com.gemstone.gemfire.cache.Region#put(Object, Object) + */ @SuppressWarnings("unchecked") - public void put(final Object key, final Object value) { + public void put(Object key, Object value) { if (value != null) { - region.put(key, value); + getNativeCache().put(key, value); } } /** - * Implementation to satisfy extension of the {@link Cache} interface in Spring 4.1. Don't add the {@link Override} - * annotation as this will break the compilation on 4.0. - * + * Implementation of {@link Cache#putIfAbsent(Object, Object)} satisfying the extension of + * the {@link Cache} interface in Spring 4.1. Don't add the {@link Override} annotation + * otherwise this will break the compilation on 4.0. + * + * @return the existing value if the given key is already mapped to a value. * @see org.springframework.cache.Cache#putIfAbsent(java.lang.Object, java.lang.Object) + * @see com.gemstone.gemfire.cache.Region#putIfAbsent(Object, Object) */ @SuppressWarnings("unchecked") public ValueWrapper putIfAbsent(Object key, Object value) { - Object existingValue = region.putIfAbsent(key, value); + Object existingValue = getNativeCache().putIfAbsent(key, value); - return (existingValue == null ? null : new SimpleValueWrapper(existingValue)); + return (existingValue != null ? new SimpleValueWrapper(existingValue) : null); } - } diff --git a/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java b/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java index c58e6524..cf854628 100644 --- a/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java +++ b/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java @@ -17,103 +17,223 @@ package org.springframework.data.gemfire.support; import java.util.Collection; -import java.util.LinkedHashSet; +import java.util.HashSet; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.Region; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.support.AbstractCacheManager; import org.springframework.util.Assert; -import com.gemstone.gemfire.cache.Region; - /** - * Spring Framework {@link CacheManager} backed by a Gemfire {@link com.gemstone.gemfire.cache.Cache}. Automatically - * discovers the created caches (or {@link Region}s in Gemfire terminology). - * + * Core Spring Framework {@link CacheManager} implementation backed by a GemFire cache instance + * (either a client or peer cache). + * + * Automatically discovers available caches (or GemFire {@link Region Regions}) when a cache for a given name + * is missing and dynamic cache lookup/creation is enabled. + * * @author Costin Leau * @author David Turanski * @author John Blum * @see org.springframework.cache.Cache * @see org.springframework.cache.CacheManager * @see org.springframework.cache.support.AbstractCacheManager - * @see com.gemstone.gemfire.cache.Cache + * @see com.gemstone.gemfire.cache.GemFireCache * @see com.gemstone.gemfire.cache.Region */ @SuppressWarnings("unused") public class GemfireCacheManager extends AbstractCacheManager { - private com.gemstone.gemfire.cache.Cache gemfireCache; + private final AtomicBoolean dynamic = new AtomicBoolean(true); - private Set> regions; + private com.gemstone.gemfire.cache.GemFireCache gemfireCache; + + private Set> regions; + + private Set cacheNames; + + /* (non-Javadoc) */ + @SuppressWarnings("all") + T assertGemFireCacheAvailable(T gemfireCache) { + Assert.state(gemfireCache != null, "A GemFire cache instance is required"); + Assert.state(!gemfireCache.isClosed(), String.format("GemFire cache [%s] has been closed", + gemfireCache.getName())); + + return gemfireCache; + } + + /* (non-Javadoc) */ + @SuppressWarnings("all") + Region assertGemFireRegionAvailable(Region region, String cacheName) { + Assert.state(region != null, String.format("No Region for cache name [%s] was found", cacheName)); + Assert.state(!region.isDestroyed(), String.format("Region [%s] has been destroyed", cacheName)); + + return region; + } /** - * Loads the GemFire Cache Regions managed by this CacheManager. + * Loads all configured GemFire {@link Region Regions} that will be used by this {@link CacheManager}. * - * @return a Collection of GemFire Cache Regions (caches) to be managed by this SDG CacheManager. + * Any GemFire {@link Region Regions} configured with the {@link #regions} property will take precedence over + * any configured {@link #cacheNames}. If no GemFire {@link Region Regions} were configured, then any + * {@link #cacheNames} that were specified will be used to lookup existing GemFire {@link Region Regions} + * to function as Spring {@link Cache Caches}in Spring's caching infrastructure. + * + * However, if neither {@link #regions} nor {@link #cacheNames} were specified, then all defined GemFire + * {@link Region Regions} declared in the Spring application context, as determined by + * {@link GemFireCache#rootRegions()}, will be used as Spring {@link Cache Caches}, and this {@link CacheManager} + * will allow any dynamically created GemFire {@link Region Regions} at runtime to be found and used as a + * Spring {@link Cache} as well. + * + * @return a {@link Collection} of GemFire {@link Region Regions} used by this {@link CacheManager} + * to function as {@link Cache Caches} in Spring's caching infrastructure. + * @throws IllegalStateException if a GemFire cache instance was not provided, the provided GemFire cache instance + * has been closed, no GemFire {@link Region} could be found for a given cache name, or the GemFire {@link Region} + * for the given cache name has been destroyed. * @see org.springframework.cache.Cache - * @see com.gemstone.gemfire.cache.Cache#rootRegions() */ @Override protected Collection loadCaches() { - if (regions == null) { - Assert.state(gemfireCache != null, "A backing GemFire Cache is required."); - Assert.state(!gemfireCache.isClosed(), "The GemFire Cache is closed; an open instance is required."); + Set> regions = resolveRegions(this.gemfireCache, this.regions, this.cacheNames); - regions = gemfireCache.rootRegions(); - } + Collection caches = new HashSet(regions.size()); - Collection caches = new LinkedHashSet(regions.size()); - - for (Region region: this.regions) { - caches.add(new GemfireCache(region)); + for (Region region : regions) { + caches.add(GemfireCache.wrap(region)); } return caches; } + /* (non-Javadoc) */ + Set> resolveRegions(GemFireCache gemfireCache, Set> regions, Set cacheNames) { + if (isSet(regions)) { + dynamic.set(false); + return regions; + } + else if (isSet(cacheNames)) { + dynamic.set(false); + + regions = new HashSet>(cacheNames.size()); + + for (String cacheName : cacheNames) { + regions.add(regionFor(gemfireCache, cacheName)); + } + + return regions; + } + else { + return assertGemFireCacheAvailable(gemfireCache).rootRegions(); + } + } + + /* (non-Javadoc) */ + boolean isSet(Iterable collection) { + return (collection != null && collection.iterator().hasNext()); + } + + /* (non-Javadoc) */ + Region regionFor(GemFireCache gemfireCache, String cacheName) { + return assertGemFireRegionAvailable(assertGemFireCacheAvailable(gemfireCache).getRegion(cacheName), cacheName); + } + /** - * Gets a Cache (GemFire Cache Region) by name. + * Returns a missing Spring {@link Cache} for the given {@code name}. * - * @param name a String indicating the name of the Cache to get. - * @return a Cache with the given name. + * To return a missing Spring {@link Cache} for the given {@code name}, dynamic cache lookup/creation must be + * enabled, which means that either the {@link #cacheNames} or {@link #regions} properties must not be set. + * If either property was specified then dynamic Spring {@link Cache} lookup/creation will be disabled and this + * overridden {@link AbstractCacheManager#getMissingCache(String)} method will return {@literal null}. + * + * @param name name of the missing Spring {@link Cache} to lookup (and potentially create). + * @return a Spring {@link Cache} instance for the given {@code name} or {@literal null} if the {@link Cache} + * cannot be found (or possibly created). + * @see org.springframework.cache.support.AbstractCacheManager#getMissingCache(String) * @see org.springframework.cache.Cache */ @Override - public Cache getCache(String name) { - Cache cache = super.getCache(name); + protected Cache getMissingCache(String name) { + Cache cache = super.getMissingCache(name); - if (cache == null) { - // check the GemFire Cache again in case the Cache (Region) was added at runtime - Region region = gemfireCache.getRegion(name); - - if (region != null) { - cache = new GemfireCache(region); - addCache(cache); - } - } - - return cache; + return (cache != null ? cache : (isDynamic() ? GemfireCache.wrap(regionFor(this.gemfireCache, name)) : null)); } /** - * Sets the GemFire Cache backing this {@link CacheManager}. - * - * @param gemfireCache the GemFire Peer Cache instance. - * @see com.gemstone.gemfire.cache.Cache + * Determines whether this {@link CacheManager} allows the dynamic creation of a {@link Cache} at runtime. + * + * @return a boolean value indicating whether dynamic {@link Cache} creation is enabled. */ - public void setCache(com.gemstone.gemfire.cache.Cache gemfireCache) { + protected boolean isDynamic() { + return dynamic.get(); + } + + /** + * Sets the GemFire cache instance backing this {@link CacheManager}. + * + * When set, if neither {@link Region Regions} nor {@code cacheNames} were specified, then this {@link CacheManager} + * is capable of creating Spring {@link Cache Caches} backed by existing GemFire {@link Region Regions} used by + * the application at runtime. However, in order to dynamically create Spring {@link Cache Caches} a reference to + * an open GemFire cache instance must be set. + * + * @param gemfireCache the GemFire cache instance used by this {@link CacheManager} + * to manage Spring {@link Cache Caches}. + * @see com.gemstone.gemfire.cache.GemFireCache + */ + public void setCache(com.gemstone.gemfire.cache.GemFireCache gemfireCache) { this.gemfireCache = gemfireCache; } - + /** - * Sets the Regions to use (alternative to injecting the GemFire Cache). + * Returns the {@link GemFireCache} instance backing this {@link CacheManager}. * - * @param regions the Set of Regions (caches) managed by this CacheManager. + * @return the {@link GemFireCache} instance backing this {@link CacheManager}. + * @see com.gemstone.gemfire.cache.GemFireCache + */ + protected com.gemstone.gemfire.cache.GemFireCache getCache() { + return this.gemfireCache; + } + + /** + * Sets the names of all Spring {@link Cache Caches} that will be used in the application. + * + * When set, this disables the dynamic capability of this {@link CacheManager} to create Spring {@link Cache Caches} + * at runtime by dynamically looking up existing {@link Region Regions} from the GemFire cache instance. + * + * @param cacheNames {@link Set} of cache names that will be used in the application. + * @see java.util.Set + */ + public void setCacheNames(Set cacheNames) { + this.cacheNames = cacheNames; + } + + /** + * Explicitly sets the GemFire {@link Region Regions} to be used as Spring {@link Cache Caches} + * in the application. + * + * When set, this disables the dynamic capability of this {@link CacheManager} to create Spring {@link Cache Caches} + * at runtime by dynamically looking up existing {@link Region Regions} from the GemFire cache instance. + * + * @param regions {@link Set} of GemFire {@link Region Regions} used by this {@link CacheManager} + * as Spring {@link Cache Caches}. * @see com.gemstone.gemfire.cache.Region */ - public void setRegions(Set> regions) { + public void setRegions(Set> regions) { this.regions = regions; } + /** + * Returns the set of GemFire {@link Region Regions} used explicitly as Spring {@link Cache Caches} + * in Spring's caching infrastructure. + * + * @return the set of GemFire {@link Region Regions} functioning as Spring {@link Cache Caches} + * in Spring's caching infrastructure + * @see com.gemstone.gemfire.cache.Region + */ + protected Set> getRegions() { + return this.regions; + } } diff --git a/src/test/java/org/springframework/data/gemfire/support/AbstractNativeCacheTest.java b/src/test/java/org/springframework/data/gemfire/support/AbstractNativeCacheTest.java deleted file mode 100644 index 637487cf..00000000 --- a/src/test/java/org/springframework/data/gemfire/support/AbstractNativeCacheTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2010 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.data.gemfire.support; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.Matchers.sameInstance; -import static org.junit.Assert.assertThat; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.cache.Cache; - -/** - * Abstract base test class for native cache implementations. - * - * @author Costin Leau - * @author John Blum - * @see org.springframework.cache.Cache - */ -public abstract class AbstractNativeCacheTest { - - protected static final String CACHE_NAME = "Example"; - - private T nativeCache; - private Cache cache; - - @Before - public void setUp() throws Exception { - nativeCache = createNativeCache(); - cache = createCache(nativeCache); - cache.clear(); - } - - @SuppressWarnings("unchecked") - protected C createCache() throws Exception { - return (C) createCache(createNativeCache()); - } - - protected abstract Cache createCache(T nativeCache); - - protected abstract T createNativeCache() throws Exception; - - @Test - public void cacheNameIsEqualToExpected() throws Exception { - assertThat(cache.getName(), is(equalTo(CACHE_NAME))); - } - - @Test - @SuppressWarnings("unchecked") - public void nativeCacheIsSameAsExpected() throws Exception { - assertThat((T) cache.getNativeCache(), is(sameInstance(nativeCache))); - } - - @Test - public void cachePutIsSuccessful() throws Exception { - assertThat(cache.get("enescu"), is(nullValue())); - cache.put("enescu", "george"); - assertThat(String.valueOf(cache.get("enescu").get()), is(equalTo("george"))); - } - - @Test - public void cacheGetForClassTypeIsSuccessful() { - cache.put("one", Boolean.TRUE); - cache.put("two", 'X'); - cache.put("three", 101); - cache.put("four", Math.PI); - cache.put("five", "TEST"); - - assertThat(cache.get("one", Boolean.class), is(equalTo(Boolean.TRUE))); - assertThat(cache.get("two", Character.class), is(equalTo('X'))); - assertThat(cache.get("three", Integer.class), is(equalTo(101))); - assertThat(cache.get("four", Double.class), is(equalTo(Math.PI))); - assertThat(cache.get("five", String.class), is(equalTo("TEST"))); - } - - @Test - public void cacheClearIsSuccessful() throws Exception { - assertThat(cache.get("enescu"), is(nullValue())); - - cache.put("enescu", "george"); - - assertThat(cache.get("vlaicu"), is(nullValue())); - - cache.put("vlaicu", "aurel"); - - assertThat(cache.get("enescu", String.class), is(equalTo("george"))); - assertThat(cache.get("vlaicu", String.class), is(equalTo("aurel"))); - - cache.clear(); - - assertThat(cache.get("vlaicu"), is(nullValue())); - assertThat(cache.get("enescu"), is(nullValue())); - } - -} diff --git a/src/test/java/org/springframework/data/gemfire/support/AbstractNativeCacheTests.java b/src/test/java/org/springframework/data/gemfire/support/AbstractNativeCacheTests.java new file mode 100644 index 00000000..fe173605 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/AbstractNativeCacheTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2010 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.data.gemfire.support; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.cache.Cache; + +/** + * Abstract base test class for native cache implementations. + * + * @author Costin Leau + * @author John Blum + * @see org.junit.Test + * @see org.springframework.cache.Cache + */ +public abstract class AbstractNativeCacheTests { + + protected static final String CACHE_NAME = "Example"; + + private T nativeCache; + private Cache cache; + + @Before + public void setUp() throws Exception { + nativeCache = newNativeCache(); + cache = newCache(nativeCache); + cache.clear(); + } + + @SuppressWarnings("unchecked") + protected C newCache() throws Exception { + return (C) newCache(newNativeCache()); + } + + protected abstract Cache newCache(T nativeCache); + + protected abstract T newNativeCache() throws Exception; + + @Test + public void cacheNameIsEqualToExpected() throws Exception { + assertThat(cache.getName()).isEqualTo(CACHE_NAME); + } + + @Test + public void nativeCacheIsSameAsExpected() throws Exception { + assertThat(cache.getNativeCache()).isSameAs(nativeCache); + } + + @Test + public void cachePutIsSuccessful() throws Exception { + assertThat(cache.get("enescu")).isNull(); + + cache.put("enescu", "george"); + + assertThat(cache.get("enescu").get()).isEqualTo("george"); + } + + @Test + public void cachePutThenClearIsSuccessful() throws Exception { + cache.put("enescu", "george"); + cache.put("vlaicu", "aurel"); + + assertThat(cache.get("enescu", String.class)).isEqualTo("george"); + assertThat(cache.get("vlaicu", String.class)).isEqualTo("aurel"); + + cache.clear(); + + assertThat(cache.get("vlaicu")).isNull(); + assertThat(cache.get("enescu")).isNull(); + } + + @Test + public void cachePutThenGetForClassTypeIsSuccessful() { + cache.put("one", Boolean.TRUE); + cache.put("two", 'X'); + cache.put("three", 101); + cache.put("four", Math.PI); + cache.put("five", "TEST"); + + assertThat(cache.get("one", Boolean.class)).isTrue(); + assertThat(cache.get("two", Character.class)).isEqualTo('X'); + assertThat(cache.get("three", Integer.class)).isEqualTo(101); + assertThat(cache.get("four", Double.class)).isEqualTo(Math.PI); + assertThat(cache.get("five", String.class)).isEqualTo("TEST"); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/support/GemfireCacheTest.java b/src/test/java/org/springframework/data/gemfire/support/GemfireCacheIntegrationTests.java similarity index 55% rename from src/test/java/org/springframework/data/gemfire/support/GemfireCacheTest.java rename to src/test/java/org/springframework/data/gemfire/support/GemfireCacheIntegrationTests.java index a5ff0f14..3f0251d4 100644 --- a/src/test/java/org/springframework/data/gemfire/support/GemfireCacheTest.java +++ b/src/test/java/org/springframework/data/gemfire/support/GemfireCacheIntegrationTests.java @@ -16,13 +16,18 @@ package org.springframework.data.gemfire.support; -import static org.hamcrest.Matchers.equalTo; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; import java.util.Properties; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; + +import edu.umd.cs.mtc.MultithreadedTestCase; +import edu.umd.cs.mtc.TestFramework; + +import com.gemstone.gemfire.cache.CacheFactory; +import com.gemstone.gemfire.cache.Region; import org.junit.Rule; import org.junit.Test; @@ -30,42 +35,35 @@ import org.junit.rules.ExpectedException; import org.springframework.cache.Cache; import org.springframework.data.gemfire.GemfireUtils; -import com.gemstone.gemfire.cache.CacheFactory; -import com.gemstone.gemfire.cache.Region; - -import edu.umd.cs.mtc.MultithreadedTestCase; -import edu.umd.cs.mtc.TestFramework; - /** - * Tests the interaction between the Spring Framework Cache Abstraction and GemFire as a provider - * using Spring Data GemFire's extension. + * Integration Tests for {@link GemfireCache}. * * @author Costin Leau * @author John Blum * @author Oliver Gierke - * @see org.junit.Rule * @see org.junit.Test * @see edu.umd.cs.mtc.MultithreadedTestCase * @see edu.umd.cs.mtc.TestFramework * @see org.springframework.cache.Cache * @see com.gemstone.gemfire.cache.Region */ -public class GemfireCacheTest extends AbstractNativeCacheTest> { +public class GemfireCacheIntegrationTests extends AbstractNativeCacheTests> { @Rule - public ExpectedException expectedException = ExpectedException.none(); + public ExpectedException exception = ExpectedException.none(); @Override - protected Cache createCache(Region nativeCache) { + protected Cache newCache(Region nativeCache) { return new GemfireCache(nativeCache); } @Override - protected Region createNativeCache() throws Exception { + protected Region newNativeCache() throws Exception { Properties gemfireProperties = new Properties(); - gemfireProperties.setProperty("name", GemfireCacheTest.class.getName()); + gemfireProperties.setProperty("name", GemfireCacheIntegrationTests.class.getName()); gemfireProperties.setProperty("mcast-port", "0"); + gemfireProperties.setProperty("locators", ""); gemfireProperties.setProperty("log-level", "warning"); com.gemstone.gemfire.cache.Cache cache = GemfireUtils.getCache(); @@ -84,11 +82,11 @@ public class GemfireCacheTest extends AbstractNativeCacheTest) null), is("value")); + assertThat(cache.get("key", (Class) null)).isEqualTo(1); } /** @@ -108,63 +106,67 @@ public class GemfireCacheTest extends AbstractNativeCacheTest cacheLoader = new TestCacheLoader("test"); + TestValueLoader valueLoader = new TestValueLoader("test"); - assertThat(cache.get("key", cacheLoader), is(equalTo("test"))); - assertThat(cacheLoader.wasCalled(), is(true)); - assertThat(((Region) cache.getNativeCache()).get("key"), is(equalTo("test"))); + assertThat(cache.get("key", valueLoader)).isEqualTo("test"); + assertThat(valueLoader.wasCalled()).isTrue(); + assertThat(((Region) cache.getNativeCache()).get("key")).isEqualTo("test"); } @Test @SuppressWarnings("unchecked") - public void cacheGetWithValueLoaderUsesValueLoaderReturningNull() throws Exception { - GemfireCache cache = createCache(); + public void cacheGetWithValueLoaderUsesValueLoaderReturnsNull() throws Exception { + GemfireCache cache = newCache(); - assertThat(cache.get("key", TestCacheLoader.NULL_VALUE), is(nullValue())); - assertThat(TestCacheLoader.NULL_VALUE.wasCalled(), is(true)); - assertThat(cache.getNativeCache().containsKey("key"), is(false)); + assertThat(cache.get("key", TestValueLoader.NULL_VALUE)).isNull(); + assertThat(TestValueLoader.NULL_VALUE.wasCalled()).isTrue(); + assertThat(cache.getNativeCache().containsKey("key")).isFalse(); } @Test @SuppressWarnings("all") - public void cacheGetWithValueLoaderUsesValueLoaderThrowingException() throws Exception { - GemfireCache cache = createCache(); + public void cacheGetWithValueLoaderUsesValueLoaderAndThrowsException() throws Exception { + GemfireCache cache = newCache(); - TestCacheLoader exceptionThrowingCacheLoader = new TestCacheLoader( - new IllegalStateException("test")); + try { + TestValueLoader exceptionThrowingValueLoader = new TestValueLoader( + new IllegalStateException("test")); - expectedException.expect(RuntimeException.class); - expectedException.expectCause(is(nullValue(IllegalStateException.class))); - expectedException.expectMessage(String.format("Failed to load value for key [key] using valueLoader [%1$s]", - exceptionThrowingCacheLoader.getClass().getName())); + exception.expect(Cache.ValueRetrievalException.class); + exception.expectCause(is(IllegalStateException.class)); - cache.get("key", exceptionThrowingCacheLoader); + cache.get("key", exceptionThrowingValueLoader); + } + finally { + assertThat(cache.getNativeCache().containsKey("key")).isFalse(); + } } @Test @@ -177,15 +179,15 @@ public class GemfireCacheTest extends AbstractNativeCacheTest cacheLoader; + private TestValueLoader cacheLoader; @Override public void initialize(){ super.initialize(); - cache = createCacheHandlesCheckedException(); + cache = newCacheHandlesCheckedException(); - cacheLoader = new TestCacheLoader("test") { + cacheLoader = new TestValueLoader("test") { @Override public String call() throws Exception { waitForTick(2); return super.call(); @@ -193,12 +195,12 @@ public class GemfireCacheTest extends AbstractNativeCacheTest T createCacheHandlesCheckedException() { + T newCacheHandlesCheckedException() { try { - return createCache(); + return newCache(); } catch (Exception e) { - throw new RuntimeException("failed to create Cache", e); + throw new RuntimeException("Failed to create Cache", e); } } @@ -210,8 +212,8 @@ public class GemfireCacheTest extends AbstractNativeCacheTest illegalCacheLoader = new TestCacheLoader("illegal"); + TestValueLoader illegalCacheLoader = new TestValueLoader("illegal"); String value = cache.get("key", illegalCacheLoader); assertTick(2); - assertThat(value, is(equalTo("test"))); - assertThat(illegalCacheLoader.wasCalled(), is(false)); + assertThat(value).isEqualTo("test"); + assertThat(illegalCacheLoader.wasCalled()).isFalse(); } } - protected static class TestCacheLoader implements Callable { + protected static class TestValueLoader implements Callable { - protected static final TestCacheLoader NULL_VALUE = new TestCacheLoader(); + protected static final TestValueLoader NULL_VALUE = new TestValueLoader(); - private volatile boolean called; + private AtomicBoolean called = new AtomicBoolean(false); private final T value; - public TestCacheLoader() { + public TestValueLoader() { this(null); } - public TestCacheLoader(T value) { + public TestValueLoader(T value) { this.value = value; } protected boolean wasCalled() { - boolean called = this.called; - this.called = false; - return called; + return called.compareAndSet(true, false); } @Override public T call() throws Exception { - called = true; + called.set(true); if (value instanceof Exception) { throw (Exception) value; diff --git a/src/test/java/org/springframework/data/gemfire/support/GemfireCacheManagerUnitTests.java b/src/test/java/org/springframework/data/gemfire/support/GemfireCacheManagerUnitTests.java new file mode 100644 index 00000000..2302e023 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/GemfireCacheManagerUnitTests.java @@ -0,0 +1,326 @@ +/* + * Copyright 2012 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.data.gemfire.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.Region; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cache.Cache; + +/** + * Unit tests for {@link GemfireCacheManager}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.runners.MockitoJUnitRunner + * @since 1.9.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class GemfireCacheManagerUnitTests { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Mock + private GemFireCache mockGemFireCache; + + private GemfireCacheManager cacheManager; + + @Mock + private Region mockRegion; + + @Before + public void setup() { + cacheManager = new GemfireCacheManager(); + } + + protected Set asSet(T... elements) { + Set set = new HashSet(elements.length); + Collections.addAll(set, elements); + return set; + } + + @SuppressWarnings("unchecked") + protected Region mockRegion(String name) { + Region mockRegion = mock(Region.class, name); + when(mockRegion.getName()).thenReturn(name); + return mockRegion; + } + + protected Region regionFor(Iterable> regions, String name) { + for (Region region : regions) { + if (region.getName().equals(name)) { + return region; + } + } + + return null; + } + + @Test + public void assertGemFireCacheAvailableWithAvailableGemFireCacheIsSuccessful() { + when(mockGemFireCache.isClosed()).thenReturn(false); + assertThat(cacheManager.assertGemFireCacheAvailable(mockGemFireCache)).isSameAs(mockGemFireCache); + verify(mockGemFireCache, times(1)).isClosed(); + verify(mockGemFireCache, times(1)).getName(); + } + + @Test + public void assertGemFireCacheAvailableWithNullThrowsIllegalStateException() { + exception.expect(IllegalStateException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage(is(equalTo("A GemFire cache instance is required"))); + + cacheManager.assertGemFireCacheAvailable(null); + } + + @Test + public void assertGemFireCacheAvailableWithNamedClosedGemFireCacheThrowsIllegalStateException() { + when(mockGemFireCache.isClosed()).thenReturn(true); + when(mockGemFireCache.getName()).thenReturn("Example"); + + try { + exception.expect(IllegalStateException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage(is(equalTo("GemFire cache [Example] has been closed"))); + + cacheManager.assertGemFireCacheAvailable(mockGemFireCache); + } + finally { + verify(mockGemFireCache,times(1)).isClosed(); + verify(mockGemFireCache, times(1)).getName(); + } + } + + @Test + public void assertGemFireRegionAvailableWithAvailableGemFireRegionIsSuccessful() { + when(mockRegion.isDestroyed()).thenReturn(false); + assertThat(cacheManager.assertGemFireRegionAvailable(mockRegion, "Example")).isSameAs(mockRegion); + verify(mockRegion, times(1)).isDestroyed(); + } + + @Test + public void assertGemFireRegionAvailableWithNullThrowIllegalStateException() { + exception.expect(IllegalStateException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage(is(equalTo("No Region for cache name [Example] was found"))); + + cacheManager.assertGemFireRegionAvailable(null, "Example"); + } + + @Test + public void assertGemFireRegionAvailableWithDestroyedGemFireRegionThrowIllegalStateException() { + when(mockRegion.isDestroyed()).thenReturn(true); + + try { + exception.expect(IllegalStateException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage(is(equalTo("Region [Example] has been destroyed"))); + + cacheManager.assertGemFireRegionAvailable(mockRegion, "Example"); + } + finally { + verify(mockRegion, times(1)).isDestroyed(); + } + } + + @Test + @SuppressWarnings("unchecked") + public void loadCachesIsSuccessful() { + Set> regions = asSet(mockRegion("one"), mockRegion("two"), mockRegion("three")); + + cacheManager.setRegions(regions); + + Collection loadedCaches = cacheManager.loadCaches(); + + assertThat(loadedCaches).isNotNull(); + assertThat(loadedCaches.size()).isEqualTo(3); + + for (Cache cache : loadedCaches) { + assertThat(cache).isInstanceOf(GemfireCache.class); + assertThat(regionFor(regions, cache.getName())).isSameAs(cache.getNativeCache()); + } + } + + @Test + public void resolveRegionsReturnsGivenRegions() { + Set> regions = asSet(mockRegion("one"), mockRegion("two")); + + assertThat(cacheManager.resolveRegions(mockGemFireCache, regions, asSet("three", "four"))).isSameAs(regions); + assertThat(cacheManager.isDynamic()).isFalse(); + + verify(mockGemFireCache, never()).getRegion(anyString()); + verify(mockGemFireCache, never()).rootRegions(); + } + + @Test + @SuppressWarnings("unchecked") + public void resolveRegionsReturnsRegionsForCacheNamesOnly() { + Region mockRegionOne = mockRegion("one"); + Region mockRegionTwo = mockRegion("two"); + Region mockRegionThree = mockRegion("three"); + + when(mockGemFireCache.getRegion(eq("one"))).thenReturn(mockRegionOne); + when(mockGemFireCache.getRegion(eq("two"))).thenReturn(mockRegionTwo); + when(mockGemFireCache.getRegion(eq("three"))).thenReturn(mockRegionThree); + + Set> regions = cacheManager.resolveRegions(mockGemFireCache, null, asSet("one", "two")); + + assertThat(regions).isNotNull(); + assertThat(regions.size()).isEqualTo(2); + assertThat(regions).containsAll(this.>asSet(mockRegionOne, mockRegionTwo)); + assertThat(cacheManager.isDynamic()).isFalse(); + + verify(mockGemFireCache, times(1)).getRegion(eq("one")); + verify(mockGemFireCache, times(1)).getRegion(eq("two")); + verify(mockGemFireCache, never()).getRegion(eq("three")); + } + + @Test + public void resolveRegionsReturnsGemFireCacheRootRegions() { + Set> rootRegions = asSet(mockRegion("one"), mockRegion("two")); + + when(mockGemFireCache.rootRegions()).thenReturn(rootRegions); + + assertThat(cacheManager.resolveRegions(mockGemFireCache, null, null)).isSameAs(rootRegions); + assertThat(cacheManager.isDynamic()).isTrue(); + + verify(mockGemFireCache, times(1)).rootRegions(); + verify(mockGemFireCache, never()).getRegion(anyString()); + } + + @Test + public void isSetForNonNullNonEmptyIterableIsTrue() { + assertThat(cacheManager.isSet(Collections.singleton(1))).isTrue(); + } + + @Test + public void isSetForNullIterableIsFalse() { + assertThat(cacheManager.isSet(null)).isFalse(); + } + + @Test + public void isSetForEmptyIterableIsFalse() { + assertThat(cacheManager.isSet(Collections.emptyList())).isFalse(); + } + + @Test + @SuppressWarnings("unchecked") + public void regionForCacheNameReturnsRegion() { + when(mockGemFireCache.isClosed()).thenReturn(false); + when(mockGemFireCache.getName()).thenReturn("regionForCacheNameReturnsRegion"); + when(mockGemFireCache.getRegion(eq("Example"))).thenReturn(mockRegion); + when(mockRegion.isDestroyed()).thenReturn(false); + + assertThat(cacheManager.regionFor(mockGemFireCache, "Example")).isSameAs(mockRegion); + + verify(mockGemFireCache, times(1)).isClosed(); + verify(mockGemFireCache, times(1)).getName(); + verify(mockGemFireCache, times(1)).getRegion(eq("Example")); + verify(mockRegion, times(1)).isDestroyed(); + } + + @Test + @SuppressWarnings("unchecked") + public void getMissingCacheReturnsMissingCache() { + Region mockRegion = mockRegion("missing"); + + when(mockGemFireCache.getRegion(eq("missing"))).thenReturn(mockRegion); + + cacheManager.setCache(mockGemFireCache); + + Cache cache = cacheManager.getMissingCache("missing"); + + assertThat(cache).isNotNull(); + assertThat(cache.getNativeCache()).isEqualTo(mockRegion); + + verify(mockGemFireCache, times(1)).getRegion(eq("missing")); + } + + @Test + public void getMissingCacheReturnsNull() { + cacheManager.setRegions(Collections.>singleton(mockRegion("one"))); + cacheManager.afterPropertiesSet(); + + assertThat(cacheManager.isDynamic()).isFalse(); + assertThat(cacheManager.getMissingCache("missing")).isNull(); + } + + @Test + public void setAndGetCache() { + assertThat(cacheManager.getCache()).isNull(); + + cacheManager.setCache(mockGemFireCache); + + assertThat(cacheManager.getCache()).isSameAs(mockGemFireCache); + + cacheManager.setCache(null); + + assertThat(cacheManager.getCache()).isNull(); + } + + @Test + public void setAndGetCacheNames() { + Set> regions = asSet(mockRegion("one"), mockRegion("two")); + + cacheManager.setRegions(regions); + cacheManager.afterPropertiesSet(); + + assertThat(cacheManager.getCacheNames()).containsAll(asSet("one", "two")); + } + + @Test + public void setAndGetRegions() { + Set> regions = asSet(mockRegion("one"), mockRegion("two")); + + assertThat(cacheManager.getRegions()).isNull(); + + cacheManager.setRegions(regions); + + assertThat(cacheManager.getRegions()).isSameAs(regions); + + cacheManager.setRegions(null); + + assertThat(cacheManager.getRegions()).isNull(); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/support/GemfireCacheUnitTests.java b/src/test/java/org/springframework/data/gemfire/support/GemfireCacheUnitTests.java new file mode 100644 index 00000000..7060aed2 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/GemfireCacheUnitTests.java @@ -0,0 +1,248 @@ +/* + * Copyright 2012 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.data.gemfire.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isA; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import java.util.concurrent.Callable; + +import com.gemstone.gemfire.cache.Region; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cache.Cache; + +/** + * Unit tests for {@link GemfireCache}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.runners.MockitoJUnitRunner + * @see org.springframework.data.gemfire.support.GemfireCache + * @see com.gemstone.gemfire.cache.Region + * @since 1.9.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class GemfireCacheUnitTests { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Mock + private Callable mockCallable; + + @Mock + private Region mockRegion; + + @Test + public void wrapIsSuccessful() { + GemfireCache gemfireCache = GemfireCache.wrap(mockRegion); + + assertThat(gemfireCache).isNotNull(); + assertThat(gemfireCache.getNativeCache()).isEqualTo(mockRegion); + } + + @Test + public void constructGemfireCacheWithNullRegion() { + exception.expect(IllegalArgumentException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage(is(equalTo("GemFire Region must not be null"))); + + new GemfireCache(null); + } + + @Test + public void getNameReturnsRegionName() { + when(mockRegion.getName()).thenReturn("Example"); + assertThat(GemfireCache.wrap(mockRegion).getName()).isEqualTo("Example"); + verify(mockRegion, times(1)).getName(); + } + + @Test + public void clearCallsRegionClear() { + GemfireCache.wrap(mockRegion).clear(); + verify(mockRegion, times(1)).clear(); + } + + @Test + public void evictCallsRegionDestoryWithKey() { + GemfireCache.wrap(mockRegion).evict("key"); + verify(mockRegion, times(1)).destroy(eq("key")); + } + + @Test + public void getReturnsValueWrapperForKey() { + when(mockRegion.get(eq("key"))).thenReturn("test"); + + Cache.ValueWrapper value = GemfireCache.wrap(mockRegion).get("key"); + + assertThat(value).isNotNull(); + assertThat(value.get()).isEqualTo("test"); + + verify(mockRegion, times(1)).get(eq("key")); + } + + @Test + public void getReturnsNullForKey() { + when(mockRegion.get(anyString())).thenReturn(null); + assertThat(GemfireCache.wrap(mockRegion).get("key")).isNull(); + verify(mockRegion, times(1)).get(eq("key")); + } + + @Test + public void getReturnsValueForKeyAsDesiredType() { + when(mockRegion.get(eq("key"))).thenReturn(1); + + Object value = GemfireCache.wrap(mockRegion).get("key", Integer.class); + + assertThat(value).isNotNull(); + assertThat(value).isInstanceOf(Integer.class); + assertThat(value).isEqualTo(1); + + verify(mockRegion, times(1)).get(eq("key")); + } + + @Test + public void getReturnsNullForKeyAsDesiredType() { + when(mockRegion.get(eq("key"))).thenReturn(null); + assertThat(GemfireCache.wrap(mockRegion).get("key", Double.class)).isNull(); + verify(mockRegion, times(1)).get(eq("key")); + } + + @Test + public void getReturnsValueForKeyWithNullDesiredType() { + when(mockRegion.get(eq("key"))).thenReturn(true); + assertThat(GemfireCache.wrap(mockRegion).get("key", (Class) null)).isTrue(); + verify(mockRegion, times(1)).get(eq("key")); + } + + @Test + public void getThrowsIllegalStateExceptionForKeyWhenValueIsNotAnInstanceOfDesiredType() { + when(mockRegion.get(eq("key"))).thenReturn(1); + + try { + exception.expect(IllegalStateException.class); + exception.expectCause(is(nullValue(Throwable.class))); + exception.expectMessage(String.format("Cached value [1] is not an instance of type [%s]", + Boolean.class.getName())); + + GemfireCache.wrap(mockRegion).get("key", Boolean.class); + } + finally { + verify(mockRegion, times(1)).get(eq("key")); + } + } + + @Test + public void getReturnsValueFromCacheForKeyWithValueLoader() { + when(mockRegion.get(eq("key"))).thenReturn("test"); + assertThat(GemfireCache.wrap(mockRegion).get("key", mockCallable)).isEqualTo("test"); + verify(mockRegion, times(1)).get(eq("key")); + verifyZeroInteractions(mockCallable); + } + + @Test + public void getReturnsValueFromCacheForKeyAfterSynchronizationWithValueLoader() { + when(mockRegion.get(eq("key"))).thenReturn(null).thenReturn("test"); + assertThat(GemfireCache.wrap(mockRegion).get("key", mockCallable)).isEqualTo("test"); + verify(mockRegion, times(2)).get(eq("key")); + verifyZeroInteractions(mockCallable); + } + + @Test + public void getReturnsValueFromValueLoaderForKeyWithValueLoader() throws Exception { + when(mockRegion.get(anyString())).thenReturn(null); + when(mockCallable.call()).thenReturn("mockValue"); + assertThat(GemfireCache.wrap(mockRegion).get("key", mockCallable)).isEqualTo("mockValue"); + verify(mockRegion, times(2)).get(eq("key")); + verify(mockCallable, times(1)).call(); + } + + @Test + public void getThrowsValueRetrievalExceptionForKeyWithValueLoader() throws Exception { + when(mockRegion.get(anyString())).thenReturn(null); + when(mockCallable.call()).thenThrow(new IllegalStateException("test")); + + try { + exception.expect(Cache.ValueRetrievalException.class); + exception.expectCause(isA(IllegalStateException.class)); + + GemfireCache.wrap(mockRegion).get("key", mockCallable); + } + finally { + verify(mockRegion, times(2)).get(eq("key")); + verify(mockCallable, times(1)).call(); + } + } + + @Test + @SuppressWarnings("unchecked") + public void putCachesValue() { + GemfireCache.wrap(mockRegion).put("key", "test"); + verify(mockRegion, times(1)).put(eq("key"), eq("test")); + } + + @Test + @SuppressWarnings("unchecked") + public void putDoesNotCacheNull() { + GemfireCache.wrap(mockRegion).put("key", null); + verify(mockRegion, never()).put(anyString(), anyObject()); + } + + @Test + @SuppressWarnings("unchecked") + public void putIfAbsentReturnsExistingValue() { + when(mockRegion.putIfAbsent(eq("key"), anyObject())).thenReturn("test"); + + Cache.ValueWrapper value = GemfireCache.wrap(mockRegion).putIfAbsent("key", "mockValue"); + + assertThat(value).isNotNull(); + assertThat(value.get()).isEqualTo("test"); + + verify(mockRegion, times(1)).putIfAbsent(eq("key"), eq("mockValue")); + } + + @Test + @SuppressWarnings("unchecked") + public void putIfAbsentReturnsNull() { + when(mockRegion.putIfAbsent(eq("key"), anyObject())).thenReturn(null); + + Cache.ValueWrapper value = GemfireCache.wrap(mockRegion).putIfAbsent("key", "mockValue"); + + assertThat(value).isNull(); + + verify(mockRegion, times(1)).putIfAbsent(eq("key"), eq("mockValue")); + } +}