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:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user