SGF-528 - Enable GemfireCacheManager to explicitly specify Cache names referring to Regions that will be used in Spring's Caching Infrastructure.

This commit is contained in:
John Blum
2016-09-23 19:07:15 -07:00
parent a68a31a052
commit 1af7ad523a
10 changed files with 1126 additions and 357 deletions

View File

@@ -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 <T> 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> T get(final Object key, final Class<T> type) {
Object value = region.get(key);
public <T> T get(Object key, Class<T> 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 <T> {@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> T get(final Object key, final Callable<T> valueLoader) {
public <T> T get(Object key, Callable<T> 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);
}
}

View File

@@ -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<Region<?,?>> regions;
private com.gemstone.gemfire.cache.GemFireCache gemfireCache;
private Set<Region<?, ?>> regions;
private Set<String> cacheNames;
/* (non-Javadoc) */
@SuppressWarnings("all")
<T extends GemFireCache> 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<Cache> 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<Region<?, ?>> regions = resolveRegions(this.gemfireCache, this.regions, this.cacheNames);
regions = gemfireCache.rootRegions();
}
Collection<Cache> caches = new HashSet<Cache>(regions.size());
Collection<Cache> caches = new LinkedHashSet<Cache>(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<Region<?, ?>> resolveRegions(GemFireCache gemfireCache, Set<Region<?, ?>> regions, Set<String> cacheNames) {
if (isSet(regions)) {
dynamic.set(false);
return regions;
}
else if (isSet(cacheNames)) {
dynamic.set(false);
regions = new HashSet<Region<?, ?>>(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<String> 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<Region<?,?>> regions) {
public void setRegions(Set<Region<?, ?>> 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<Region<?, ?>> getRegions() {
return this.regions;
}
}