SGF-547 - Configure Eviction with annotations.

This commit is contained in:
John Blum
2016-12-05 11:02:32 -08:00
parent bfb3347056
commit fffb2b0de6
10 changed files with 1391 additions and 383 deletions

View File

@@ -16,15 +16,15 @@
package org.springframework.data.gemfire;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import com.gemstone.gemfire.cache.EvictionAction;
import com.gemstone.gemfire.cache.EvictionAttributes;
import com.gemstone.gemfire.cache.util.ObjectSizer;
import com.gemstone.gemfire.internal.cache.lru.LRUCapacityController;
import com.gemstone.gemfire.internal.cache.lru.MemLRUCapacityController;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
/**
* Simple utility class used for defining nested factory-method like definitions w/o polluting the container with useless beans.
*
@@ -53,6 +53,9 @@ public class EvictionAttributesFactoryBean implements FactoryBean<EvictionAttrib
private ObjectSizer objectSizer = null;
/**
* @inheritDoc
*/
public void afterPropertiesSet() {
evictionAttributes = createAttributes();
}
@@ -75,17 +78,23 @@ public class EvictionAttributesFactoryBean implements FactoryBean<EvictionAttrib
}
}
/* non-Javadoc */
/**
* @inheritDoc
*/
public EvictionAttributes getObject() {
return evictionAttributes;
}
/* non-Javadoc */
/**
* @inheritDoc
*/
public Class<?> getObjectType() {
return (evictionAttributes != null ? evictionAttributes.getClass() : EvictionAttributes.class);
}
/* non-Javadoc */
/**
* @inheritDoc
*/
public boolean isSingleton() {
return true;
}
@@ -169,5 +178,4 @@ public class EvictionAttributesFactoryBean implements FactoryBean<EvictionAttrib
public EvictionPolicyType getType() {
return type;
}
}

View File

@@ -16,12 +16,12 @@
package org.springframework.data.gemfire;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import com.gemstone.gemfire.cache.AttributesFactory;
import com.gemstone.gemfire.cache.RegionAttributes;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
/**
* Spring-friendly bean for creating {@link RegionAttributes}. Eliminates the need of using a XML 'factory-method' tag.
*
@@ -32,22 +32,39 @@ import com.gemstone.gemfire.cache.RegionAttributes;
* @see com.gemstone.gemfire.cache.AttributesFactory
* @see com.gemstone.gemfire.cache.RegionAttributes
*/
@SuppressWarnings({ "deprecation", "unused" })
public class RegionAttributesFactoryBean extends AttributesFactory implements FactoryBean<RegionAttributes>,
InitializingBean {
@SuppressWarnings({ "unused" })
public class RegionAttributesFactoryBean extends AttributesFactory
implements FactoryBean<RegionAttributes>, InitializingBean {
private RegionAttributes attributes;
/**
* @inheritDoc
*/
@Override
public void afterPropertiesSet() throws Exception {
attributes = super.create();
}
/**
* @inheritDoc
*/
@Override
public RegionAttributes getObject() throws Exception {
return attributes;
}
/**
* @inheritDoc
*/
@Override
public Class<?> getObjectType() {
return (attributes != null ? attributes.getClass() : RegionAttributes.class);
}
/**
* @inheritDoc
*/
@Override
public boolean isSingleton() {
return true;
@@ -56,10 +73,4 @@ public class RegionAttributesFactoryBean extends AttributesFactory implements Fa
public void setIndexUpdateType(final IndexMaintenancePolicyType indexUpdateType) {
indexUpdateType.setIndexMaintenance(this);
}
@Override
public void afterPropertiesSet() throws Exception {
attributes = super.create();
}
}

View File

@@ -18,17 +18,6 @@ package org.springframework.data.gemfire;
import java.lang.reflect.Field;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.io.Resource;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.support.RegionShortcutWrapper;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import com.gemstone.gemfire.cache.AttributesFactory;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.CacheListener;
@@ -36,6 +25,7 @@ import com.gemstone.gemfire.cache.CacheLoader;
import com.gemstone.gemfire.cache.CacheWriter;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.EvictionAction;
import com.gemstone.gemfire.cache.EvictionAttributes;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.PartitionAttributes;
import com.gemstone.gemfire.cache.PartitionAttributesFactory;
@@ -48,6 +38,18 @@ import com.gemstone.gemfire.cache.asyncqueue.AsyncEventQueue;
import com.gemstone.gemfire.cache.wan.GatewaySender;
import com.gemstone.gemfire.internal.cache.UserSpecifiedRegionAttributes;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.io.Resource;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.support.RegionShortcutWrapper;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* Base class for FactoryBeans used to create GemFire {@link Region}s. Will try
* to first locate the region (by name) and, in case none if found, proceed to
@@ -74,6 +76,8 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
private Boolean enableGateway;
private Boolean persistent;
private AsyncEventQueue[] asyncEventQueues;
private CacheListener<K, V>[] cacheListeners;
private CacheLoader<K, V> cacheLoader;
@@ -82,8 +86,9 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
private DataPolicy dataPolicy;
private Object[] asyncEventQueues;
private Object[] gatewaySenders;
private EvictionAttributes evictionAttributes;
private GatewaySender[] gatewaySenders;
private RegionAttributes<K, V> attributes;
@@ -96,14 +101,20 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
private String diskStoreName;
private String hubId;
/**
* @inheritDoc
*/
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
postProcess(getRegion());
}
/**
* @inheritDoc
*/
@Override
@SuppressWarnings("deprecation")
@SuppressWarnings({ "unchecked" })
protected Region<K, V> lookupFallback(GemFireCache gemfireCache, String regionName) throws Exception {
Assert.isTrue(gemfireCache instanceof Cache, String.format("Unable to create Regions from '%1$s'.",
gemfireCache));
@@ -134,16 +145,12 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
}
}
if (!ObjectUtils.isEmpty(asyncEventQueues)) {
for (Object asyncEventQueue : asyncEventQueues) {
regionFactory.addAsyncEventQueueId(((AsyncEventQueue) asyncEventQueue).getId());
}
for (AsyncEventQueue asyncEventQueue : ArrayUtils.nullSafeArray(asyncEventQueues, AsyncEventQueue.class)) {
regionFactory.addAsyncEventQueueId(asyncEventQueue.getId());
}
if (!ObjectUtils.isEmpty(cacheListeners)) {
for (CacheListener<K, V> listener : cacheListeners) {
regionFactory.addCacheListener(listener);
}
for (CacheListener<K, V> listener : ArrayUtils.nullSafeArray(cacheListeners, CacheListener.class)) {
regionFactory.addCacheListener(listener);
}
if (cacheLoader != null) {
@@ -160,6 +167,10 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
regionFactory.setDiskStoreName(diskStoreName);
}
if (evictionAttributes != null) {
regionFactory.setEvictionAttributes(evictionAttributes);
}
if (scope != null) {
regionFactory.setScope(scope);
}
@@ -206,7 +217,7 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
* @see com.gemstone.gemfire.cache.Cache#createRegionFactory(com.gemstone.gemfire.cache.RegionShortcut)
* @see com.gemstone.gemfire.cache.RegionFactory
*/
protected RegionFactory<K, V> createRegionFactory(final Cache cache) {
protected RegionFactory<K, V> createRegionFactory(Cache cache) {
if (shortcut != null) {
RegionFactory<K, V> regionFactory = mergeRegionAttributes(
cache.<K, V>createRegionFactory(shortcut), attributes);
@@ -236,14 +247,14 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
* @see com.gemstone.gemfire.cache.DataPolicy
*/
@SuppressWarnings({ "deprecation", "unchecked" })
DataPolicy getDataPolicy(final RegionFactory regionFactory) {
DataPolicy getDataPolicy(RegionFactory regionFactory) {
return ((RegionAttributes) getFieldValue(getFieldValue(regionFactory, "attrsFactory", AttributesFactory.class),
"regionAttributes", null)).getDataPolicy();
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
private <T> T getFieldValue(final Object source, final String fieldName, final Class<T> targetType) {
private <T> T getFieldValue(Object source, String fieldName, Class<T> targetType) {
Field field = ReflectionUtils.findField(source.getClass(), fieldName, targetType);
ReflectionUtils.makeAccessible(field);
return (T) ReflectionUtils.getField(field, source);
@@ -269,8 +280,8 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
* @see com.gemstone.gemfire.cache.RegionFactory
*/
@SuppressWarnings("unchecked")
protected <K, V> RegionFactory<K, V> mergeRegionAttributes(final RegionFactory<K, V> regionFactory,
final RegionAttributes<K, V> regionAttributes) {
protected <K, V> RegionFactory<K, V> mergeRegionAttributes(RegionFactory<K, V> regionFactory,
RegionAttributes<K, V> regionAttributes) {
if (regionAttributes != null) {
// NOTE this validation may not be strictly required depending on how the RegionAttributes were "created",
@@ -314,6 +325,7 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
return regionFactory;
}
/* (non-Javadoc) */
protected <K, V> void mergePartitionAttributes(final RegionFactory<K, V> regionFactory,
final RegionAttributes<K, V> regionAttributes) {
@@ -351,7 +363,7 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
* @see com.gemstone.gemfire.cache.AttributesFactory#validateAttributes(:RegionAttributes)
*/
@SuppressWarnings("deprecation")
void validateRegionAttributes(final RegionAttributes regionAttributes) {
void validateRegionAttributes(RegionAttributes regionAttributes) {
com.gemstone.gemfire.cache.AttributesFactory.validateAttributes(regionAttributes);
}
@@ -447,7 +459,8 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
* @param regionFactory the GemFire RegionFactory used to create the Region for post-processing.
* @see com.gemstone.gemfire.cache.RegionFactory
*/
protected void postProcess(RegionFactory<K, V> regionFactory) {
protected RegionFactory<K, V> postProcess(RegionFactory<K, V> regionFactory) {
return regionFactory;
}
/**
@@ -457,7 +470,8 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
* @param region the GemFire Region to post-process.
* @see com.gemstone.gemfire.cache.Region
*/
protected void postProcess(Region<K, V> region) {
protected Region<K, V> postProcess(Region<K, V> region) {
return region;
}
/**
@@ -547,7 +561,7 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
*
* @param asyncEventQueues defined as Object for backwards compatibility with Gemfire 6.
*/
public void setAsyncEventQueues(Object[] asyncEventQueues) {
public void setAsyncEventQueues(AsyncEventQueue[] asyncEventQueues) {
this.asyncEventQueues = asyncEventQueues;
}
@@ -674,12 +688,16 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
this.enableGateway = enableGateway;
}
public void setEvictionAttributes(EvictionAttributes evictionAttributes) {
this.evictionAttributes = evictionAttributes;
}
/**
*
* @param gatewaySenders defined as Object for backward compatibility with
* Gemfire 6
*/
public void setGatewaySenders(Object[] gatewaySenders) {
public void setGatewaySenders(GatewaySender[] gatewaySenders) {
this.gatewaySenders = gatewaySenders;
}
@@ -730,11 +748,11 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
this.snapshot = snapshot;
}
/*
* (non-Javadoc)
* @see org.springframework.context.Lifecycle#start()
/**
* @inheritDoc
*/
@Override
@SuppressWarnings("all")
public void start() {
if (!ObjectUtils.isEmpty(gatewaySenders)) {
synchronized (gatewaySenders) {
@@ -750,9 +768,17 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
this.running = true;
}
/*
* (non-Javadoc)
* @see org.springframework.context.Lifecycle#stop()
/**
* @inheritDoc
*/
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
/**
* @inheritDoc
*/
@Override
public void stop() {
@@ -767,41 +793,27 @@ public abstract class RegionFactoryBean<K, V> extends RegionLookupFactoryBean<K,
this.running = false;
}
/*
* (non-Javadoc)
* @see org.springframework.context.Lifecycle#isRunning()
/**
* @inheritDoc
*/
@Override
public boolean isRunning() {
return this.running;
}
/*
* (non-Javadoc)
* @see org.springframework.context.Phased#getPhase()
/**
* @inheritDoc
*/
@Override
public int getPhase() {
return Integer.MAX_VALUE;
}
/*
* (non-Javadoc)
* @see org.springframework.context.SmartLifecycle#isAutoStartup()
/**
* @inheritDoc
*/
@Override
public boolean isAutoStartup() {
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.context.SmartLifecycle#stop(java.lang.Runnable)
*/
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
}

View File

@@ -16,6 +16,9 @@
package org.springframework.data.gemfire;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.Region;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanInitializationException;
@@ -25,9 +28,6 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.Region;
/**
* Simple FactoryBean for retrieving generic GemFire {@link Region}s. If lookups are not enabled or the Region
* does not exist, an exception is thrown. For declaring and configuring new Regions, see {@link RegionFactoryBean}.
@@ -49,33 +49,37 @@ public abstract class RegionLookupFactoryBean<K, V> implements FactoryBean<Regio
private GemFireCache cache;
private Region<?, ?> parent;
private volatile Region<K, V> region;
private String beanName;
private String name;
private String regionName;
/**
* @inheritDoc
*/
@Override
@SuppressWarnings("all")
public void afterPropertiesSet() throws Exception {
Assert.notNull(cache, "the 'cache' reference property must be set");
Assert.notNull(this.cache, "A 'Cache' reference must be set");
String regionName = (StringUtils.hasText(this.regionName) ? this.regionName
: (StringUtils.hasText(name) ? name : beanName));
String regionName = resolveRegionName();
Assert.hasText(regionName, "'regionName', 'name' or 'beanName' property must be set");
synchronized (cache) {
//region = (getParent() != null ? getParent().getSubregion(regionName) : cache.getRegion(regionName));
synchronized (this.cache) {
if (isLookupEnabled()) {
if (getParent() != null) {
region = getParent().getSubregion(regionName);
this.region = getParent().getSubregion(regionName);
}
else {
region = cache.getRegion(regionName);
this.region = this.cache.getRegion(regionName);
}
}
if (region != null) {
log.info(String.format("found Region (%1$s) in Cache (%2$s)", regionName, cache.getName()));
log.info(String.format("Found Region [%1$s] in Cache [%2$s]", regionName, cache.getName()));
}
else {
region = lookupFallback(cache, regionName);
@@ -84,36 +88,69 @@ public abstract class RegionLookupFactoryBean<K, V> implements FactoryBean<Regio
}
/**
* Fallback method in case the named Region does not exist. By default, this implementation throws an exception.
*
* @param cache a reference to the GemFire Cache.
* @param regionName the name of the GemFire Cache Region.
* @return the Region in the GemFire Cache with the given name.
* @throws Exception if the lookup operation fails.
* @deprecated call {@link #lookupRegion(GemFireCache, String)} instead.
*/
@Deprecated
protected Region<K, V> lookupFallback(GemFireCache cache, String regionName) throws Exception {
throw new BeanInitializationException(String.format("Cannot find Region [%1$s] in Cache [%2$s].",
regionName, cache));
return lookupRegion(cache, regionName);
}
/**
* Method to perform a lookup when the named {@link Region} does not exist. By default, this implementation
* throws an exception.
*
* @param cache reference to the GemFire cache.
* @param regionName name of the GemFire {@link Region}.
* @return the {@link Region} in the GemFire cache with the given name.
* @throws BeanInitializationException if the lookup operation fails.
* @see com.gemstone.gemfire.cache.Region
*/
protected Region<K, V> lookupRegion(GemFireCache cache, String regionName) throws Exception {
throw new BeanInitializationException(String.format(
"Region [%1$s] in Cache [%2$s] not found", regionName, cache));
}
/**
* @inheritDoc
*/
@Override
public Region<K, V> getObject() throws Exception {
return getRegion();
}
/**
* @inheritDoc
*/
@Override
public Class<?> getObjectType() {
Region localRegion = getRegion();
return (localRegion != null ? localRegion.getClass() : Region.class);
Region region = getRegion();
return (region != null ? region.getClass() : Region.class);
}
/**
* @inheritDoc
*/
@Override
public boolean isSingleton() {
return true;
}
/**
* Sets the name of the Cache Region based on the bean 'id' attribute. If no Region is found for the given name,
* a new one will be created.
* Resolves the name of the GemFire {@link Region}.
*
* @param name the name of this bean (Region) in the application context (bean factory).
* @return a {@link String} indicating the name of the GemFire {@link Region}.
* @see com.gemstone.gemfire.cache.Region#getName()
*/
public String resolveRegionName() {
return (StringUtils.hasText(this.regionName) ? this.regionName
: (StringUtils.hasText(this.name) ? this.name : this.beanName));
}
/**
* Sets the name of the {@link Region} based on the bean 'id' attribute. If no {@link Region} is found
* with the given name, a new one will be created.
*
* @param name name of this {@link Region} bean in the Spring {@link org.springframework.context.ApplicationContext}.
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(String)
*/
public void setBeanName(String name) {
@@ -121,9 +158,9 @@ public abstract class RegionLookupFactoryBean<K, V> implements FactoryBean<Regio
}
/**
* Sets a reference to the Cache used to create the Region.
* Sets a reference to the {@link GemFireCache} used to create the {@link Region}.
*
* @param cache a reference to the Cache.
* @param cache reference to the {@link GemFireCache}.
* @see org.springframework.data.gemfire.CacheFactoryBean
* @see com.gemstone.gemfire.cache.GemFireCache
*/
@@ -131,60 +168,6 @@ public abstract class RegionLookupFactoryBean<K, V> implements FactoryBean<Regio
this.cache = cache;
}
/**
* Sets the name of the Cache Region based on the bean 'name' attribute. If no Region is found with the given name,
* a new one will be created. If no name is given, the value of the 'beanName' property will be used.
*
* @param name the region name
* @see #setBeanName(String)
* @see com.gemstone.gemfire.cache.Region#getFullPath()
*/
public void setName(String name) {
this.name = name;
}
/**
* Sets a reference to the parent Region if this FactoryBean represents a GemFire Cache Sub-Region.
*
* @param parent a reference to the parent Region if this Region is a Sub-Region.
* @see com.gemstone.gemfire.cache.Region
*/
public void setParent(Region<?, ?> parent) {
this.parent = parent;
}
/**
* Gets a reference to the parent Region if this FactoryBean represents a GemFire Cache Sub-Region.
*
* @return a reference to the parent Region or null if this Region is not a Sub-Region.
* @see com.gemstone.gemfire.cache.Region
*/
protected Region<?, ?> getParent() {
return parent;
}
/**
* Gets the reference to the GemFire Region obtained by this Spring FactoryBean during the lookup operation.
*
* @return a reference to the GemFire Region found during lookup.
* @see com.gemstone.gemfire.cache.Region
*/
protected Region<K, V> getRegion() {
return region;
}
/**
* Sets the name of the Cache Region as expected by GemFire. If no Region is found with the given name, a new one
* will be created. If no name is given, the value of the 'name' property will be used.
*
* @param regionName a String indicating the name of the Region in GemFire.
* @see #setName(String)
* @see com.gemstone.gemfire.cache.Region#getName()
*/
public void setRegionName(String regionName) {
this.regionName = regionName;
}
/* (non-Javadoc) */
boolean isLookupEnabled() {
return Boolean.TRUE.equals(getLookupEnabled());
@@ -200,4 +183,60 @@ public abstract class RegionLookupFactoryBean<K, V> implements FactoryBean<Regio
return lookupEnabled;
}
/**
* Sets the name of the cache {@link Region} based on the bean 'name' attribute. If no {@link Region} is found
* with the given name, a new one will be created. If no name is given, the value of the 'beanName' property
* will be used.
*
* @param name {@link Region} name.
* @see #setBeanName(String)
*/
public void setName(String name) {
this.name = name;
}
/**
* Sets a reference to the parent {@link Region} to indicated this {@link FactoryBean} represents a GemFire cache
* {@link Region Sub-Region}.
*
* @param parent reference to the parent {@link Region}.
* @see com.gemstone.gemfire.cache.Region
*/
public void setParent(Region<?, ?> parent) {
this.parent = parent;
}
/**
* Returns a reference to the parent {@link Region} indicating this {@link FactoryBean} represents a GemFire cache
* {@link Region Sub-Region}.
*
* @return a reference to the parent {@link Region} or {@literal null} if this {@link Region}
* is not a {@link Region Sub-Region}.
* @see com.gemstone.gemfire.cache.Region
*/
protected Region<?, ?> getParent() {
return this.parent;
}
/**
* Returns a reference to the GemFire {@link Region} resolved by this Spring {@link FactoryBean}
* during the lookup operation; maybe a new {@link Region}.
*
* @return a reference to the GemFire {@link Region} resolved during lookup.
* @see com.gemstone.gemfire.cache.Region
*/
protected Region<K, V> getRegion() {
return this.region;
}
/**
* Sets the name of the cache {@link Region}. If no {@link Region} is found with the given name,
* a new one will be created. If no name is given, the value of the 'name' property will be used.
*
* @param regionName name of the {@link Region}.
* @see #setName(String)
*/
public void setRegionName(String regionName) {
this.regionName = regionName;
}
}

View File

@@ -16,25 +16,11 @@
package org.springframework.data.gemfire.client;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.core.io.Resource;
import org.springframework.data.gemfire.DataPolicyConverter;
import org.springframework.data.gemfire.GemfireUtils;
import org.springframework.data.gemfire.RegionLookupFactoryBean;
import org.springframework.data.gemfire.config.xml.GemfireConstants;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.gemstone.gemfire.cache.CacheListener;
import com.gemstone.gemfire.cache.CacheLoader;
import com.gemstone.gemfire.cache.CacheWriter;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.EvictionAttributes;
import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.RegionAttributes;
@@ -43,8 +29,25 @@ import com.gemstone.gemfire.cache.client.ClientRegionFactory;
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
import com.gemstone.gemfire.cache.client.Pool;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.core.io.Resource;
import org.springframework.data.gemfire.DataPolicyConverter;
import org.springframework.data.gemfire.GemfireUtils;
import org.springframework.data.gemfire.RegionLookupFactoryBean;
import org.springframework.data.gemfire.config.xml.GemfireConstants;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Client extension for GemFire Regions.
* Spring {@link FactoryBean} used to create a GemFire client {@link Region}.
*
* @author Costin Leau
* @author David Turanski
@@ -53,9 +56,6 @@ import com.gemstone.gemfire.cache.client.Pool;
* @see org.springframework.beans.factory.BeanFactoryAware
* @see org.springframework.beans.factory.DisposableBean
* @see org.springframework.data.gemfire.RegionLookupFactoryBean
* @see com.gemstone.gemfire.cache.CacheListener
* @see com.gemstone.gemfire.cache.CacheLoader
* @see com.gemstone.gemfire.cache.CacheWriter
* @see com.gemstone.gemfire.cache.DataPolicy
* @see com.gemstone.gemfire.cache.GemFireCache
* @see com.gemstone.gemfire.cache.Region
@@ -87,6 +87,8 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
private DataPolicy dataPolicy;
private EvictionAttributes evictionAttributes;
private Interest<K>[] interests;
private RegionAttributes<K, V> attributes;
@@ -96,75 +98,61 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
private String diskStoreName;
private String poolName;
/**
* @inheritDoc
*/
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
postProcess(getRegion());
}
/**
* @inheritDoc
*/
@Override
@SuppressWarnings("all")
protected Region<K, V> lookupFallback(GemFireCache cache, String regionName) throws Exception {
Assert.isTrue(GemfireUtils.isClient(cache), "A ClientCache is required to create a client Region");
ClientRegionFactory<K, V> clientRegionFactory = ((ClientCache) cache).createClientRegionFactory(
resolveClientRegionShortcut());
if (attributes != null) {
clientRegionFactory.setCloningEnabled(attributes.getCloningEnabled());
clientRegionFactory.setCompressor(attributes.getCompressor());
clientRegionFactory.setConcurrencyChecksEnabled(attributes.getConcurrencyChecksEnabled());
clientRegionFactory.setConcurrencyLevel(attributes.getConcurrencyLevel());
clientRegionFactory.setCustomEntryIdleTimeout(attributes.getCustomEntryIdleTimeout());
clientRegionFactory.setCustomEntryTimeToLive(attributes.getCustomEntryTimeToLive());
clientRegionFactory.setDiskStoreName(attributes.getDiskStoreName());
clientRegionFactory.setDiskSynchronous(attributes.isDiskSynchronous());
clientRegionFactory.setEntryIdleTimeout(attributes.getEntryIdleTimeout());
clientRegionFactory.setEntryTimeToLive(attributes.getEntryTimeToLive());
clientRegionFactory.setEvictionAttributes(attributes.getEvictionAttributes());
clientRegionFactory.setInitialCapacity(attributes.getInitialCapacity());
clientRegionFactory.setKeyConstraint(attributes.getKeyConstraint());
clientRegionFactory.setLoadFactor(attributes.getLoadFactor());
clientRegionFactory.setPoolName(attributes.getPoolName());
clientRegionFactory.setRegionIdleTimeout(attributes.getRegionIdleTimeout());
clientRegionFactory.setRegionTimeToLive(attributes.getRegionTimeToLive());
clientRegionFactory.setStatisticsEnabled(attributes.getStatisticsEnabled());
clientRegionFactory.setValueConstraint(attributes.getValueConstraint());
}
String poolName = resolvePoolName();
if (StringUtils.hasText(poolName)) {
clientRegionFactory.setPoolName(eagerlyInitializePool(poolName));
}
ClientRegionFactory<K, V> clientRegionFactory =
((ClientCache) cache).createClientRegionFactory(resolveClientRegionShortcut());
setAttributes(clientRegionFactory);
addCacheListeners(clientRegionFactory);
setDiskStoreName(clientRegionFactory);
setEvictionAttributes(clientRegionFactory);
setPoolName(clientRegionFactory);
if (diskStoreName != null) {
clientRegionFactory.setDiskStoreName(diskStoreName);
}
Region<K, V> clientRegion = (getParent() != null ? clientRegionFactory.createSubregion(getParent(), regionName)
: clientRegionFactory.create(regionName));
if (log.isInfoEnabled()) {
if (getParent() != null) {
log.info(String.format("Created new Client Cache sub-Region [%1$s] under parent Region [%2$s].",
regionName, getParent().getName()));
}
else {
log.info(String.format("Created new Client Cache Region [%1$s].", regionName));
}
}
if (snapshot != null) {
clientRegion.loadSnapshot(snapshot.getInputStream());
}
return clientRegion;
return logCreateRegionEvent(create(clientRegionFactory, regionName));
}
/* (non-Javadoc) */
private Region<K, V> create(ClientRegionFactory<K, V> clientRegionFactory, String regionName) {
return (getParent() != null ? clientRegionFactory.createSubregion(getParent(), regionName)
: clientRegionFactory.create(regionName));
}
/* (non-Javadoc) */
private Region<K, V> logCreateRegionEvent(Region<K, V> region) {
if (log.isInfoEnabled()) {
if (getParent() != null) {
log.info(String.format("Created new client cache Sub-Region [%1$s] under parent Region [%2$s].",
region.getName(), getParent().getName()));
}
else {
log.info(String.format("Created new client cache Region [%s].", region.getName()));
}
}
return region;
}
/**
* Resolves the {@link ClientRegionShortcut} used to configure the data policy of the client {@link Region}.
*
* @return a {@link ClientRegionShortcut} used to configure the data policy of the client {@link Region}.
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
*/
ClientRegionShortcut resolveClientRegionShortcut() {
ClientRegionShortcut resolvedShortcut = this.shortcut;
@@ -182,10 +170,10 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
resolvedShortcut = ClientRegionShortcut.LOCAL_PERSISTENT;
}
else {
// NOTE the Data Policy validation is based on the ClientRegionShortcut initialization logic
// NOTE the DataPolicy validation is based on the ClientRegionShortcut initialization logic
// in com.gemstone.gemfire.internal.cache.GemFireCacheImpl.initializeClientRegionShortcuts
throw new IllegalArgumentException(String.format(
"Data Policy '%1$s' is invalid for Client Regions.", this.dataPolicy));
throw new IllegalArgumentException(String.format("Data Policy '%s' is invalid for Client Regions",
this.dataPolicy));
}
}
else {
@@ -194,43 +182,13 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
}
}
// NOTE the ClientRegionShortcut and Persistent attribute will be compatible if the shortcut was derived from
// the Data Policy.
// NOTE the ClientRegionShortcut and Persistent attribute will be compatible if the shortcut
// was derived from the Data Policy.
assertClientRegionShortcutAndPersistentAttributeAreCompatible(resolvedShortcut);
return resolvedShortcut;
}
/* (non-Javadoc) */
String resolvePoolName() {
String poolName = this.poolName;
if (!StringUtils.hasText(poolName)) {
String defaultPoolName = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME;
poolName = (beanFactory.containsBean(defaultPoolName) ? defaultPoolName : poolName);
}
return poolName;
}
/* (non-Javadoc) */
String eagerlyInitializePool(String poolName) {
try {
if (beanFactory.isTypeMatch(poolName, Pool.class)) {
if (log.isDebugEnabled()) {
log.debug(String.format("Found bean definition for Pool [%1$s]; Eagerly initializing...", poolName));
}
beanFactory.getBean(poolName, Pool.class);
}
}
catch (BeansException ignore) {
log.warn(ignore.getMessage());
}
return poolName;
}
/**
* Validates that the settings for ClientRegionShortcut and the 'persistent' attribute in &lt;gfe:*-region&gt; elements
* are compatible.
@@ -241,17 +199,17 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
* @see #isNotPersistent()
* @see com.gemstone.gemfire.cache.client.ClientRegionShortcut
*/
protected void assertClientRegionShortcutAndPersistentAttributeAreCompatible(final ClientRegionShortcut resolvedShortcut) {
private void assertClientRegionShortcutAndPersistentAttributeAreCompatible(ClientRegionShortcut resolvedShortcut) {
final boolean persistentNotSpecified = (this.persistent == null);
if (ClientRegionShortcut.LOCAL_PERSISTENT.equals(resolvedShortcut)
|| ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW.equals(resolvedShortcut)) {
|| ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW.equals(resolvedShortcut)) {
Assert.isTrue(persistentNotSpecified || isPersistent(), String.format(
"Client Region Shortcut '%1$s' is invalid when persistent is false.", resolvedShortcut));
"Client Region Shortcut '%s' is invalid when persistent is false", resolvedShortcut));
}
else {
Assert.isTrue(persistentNotSpecified || isNotPersistent(), String.format(
"Client Region Shortcut '%1$s' is invalid when persistent is true.", resolvedShortcut));
"Client Region Shortcut '%s' is invalid when persistent is true", resolvedShortcut));
}
}
@@ -265,43 +223,147 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
* @see #isNotPersistent()
* @see com.gemstone.gemfire.cache.DataPolicy
*/
protected void assertDataPolicyAndPersistentAttributeAreCompatible(final DataPolicy resolvedDataPolicy) {
private void assertDataPolicyAndPersistentAttributeAreCompatible(DataPolicy resolvedDataPolicy) {
if (resolvedDataPolicy.withPersistence()) {
Assert.isTrue(isPersistentUnspecified() || isPersistent(), String.format(
"Data Policy '%1$s' is invalid when persistent is false.", resolvedDataPolicy));
"Data Policy '%s' is invalid when persistent is false", resolvedDataPolicy));
}
else {
// NOTE otherwise, the Data Policy is without persistence, so...
Assert.isTrue(isPersistentUnspecified() || isNotPersistent(), String.format(
"Data Policy '%1$s' is invalid when persistent is true.", resolvedDataPolicy));
"Data Policy '%s' is invalid when persistent is true", resolvedDataPolicy));
}
}
private void addCacheListeners(ClientRegionFactory<K, V> factory) {
if (attributes != null) {
CacheListener<K, V>[] cacheListeners = attributes.getCacheListeners();
/* (non-Javadoc) */
private ClientRegionFactory<K, V> setAttributes(ClientRegionFactory<K, V> clientRegionFactory) {
RegionAttributes<K, V> localAttributes = this.attributes;
if (!ObjectUtils.isEmpty(cacheListeners)) {
for (CacheListener<K, V> cacheListener : cacheListeners) {
factory.addCacheListener(cacheListener);
if (localAttributes != null) {
clientRegionFactory.setCloningEnabled(localAttributes.getCloningEnabled());
clientRegionFactory.setCompressor(localAttributes.getCompressor());
clientRegionFactory.setConcurrencyChecksEnabled(localAttributes.getConcurrencyChecksEnabled());
clientRegionFactory.setConcurrencyLevel(localAttributes.getConcurrencyLevel());
clientRegionFactory.setCustomEntryIdleTimeout(localAttributes.getCustomEntryIdleTimeout());
clientRegionFactory.setCustomEntryTimeToLive(localAttributes.getCustomEntryTimeToLive());
clientRegionFactory.setDiskStoreName(localAttributes.getDiskStoreName());
clientRegionFactory.setDiskSynchronous(localAttributes.isDiskSynchronous());
clientRegionFactory.setEntryIdleTimeout(localAttributes.getEntryIdleTimeout());
clientRegionFactory.setEntryTimeToLive(localAttributes.getEntryTimeToLive());
clientRegionFactory.setEvictionAttributes(localAttributes.getEvictionAttributes());
clientRegionFactory.setInitialCapacity(localAttributes.getInitialCapacity());
clientRegionFactory.setKeyConstraint(localAttributes.getKeyConstraint());
clientRegionFactory.setLoadFactor(localAttributes.getLoadFactor());
clientRegionFactory.setPoolName(localAttributes.getPoolName());
clientRegionFactory.setRegionIdleTimeout(localAttributes.getRegionIdleTimeout());
clientRegionFactory.setRegionTimeToLive(localAttributes.getRegionTimeToLive());
clientRegionFactory.setStatisticsEnabled(localAttributes.getStatisticsEnabled());
clientRegionFactory.setValueConstraint(localAttributes.getValueConstraint());
}
return clientRegionFactory;
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
private ClientRegionFactory<K, V> addCacheListeners(ClientRegionFactory<K, V> clientRegionFactory) {
for (CacheListener<K, V> cacheListener : this.<K, V>attributesCacheListeners()) {
clientRegionFactory.addCacheListener(cacheListener);
}
for (CacheListener<K, V> cacheListener : ArrayUtils.nullSafeArray(this.cacheListeners, CacheListener.class)) {
clientRegionFactory.addCacheListener(cacheListener);
}
return clientRegionFactory;
}
/* (non-Javadoc) */
@SuppressWarnings("unchecked")
private <K, V> CacheListener<K, V>[] attributesCacheListeners() {
return (this.attributes != null
? ArrayUtils.nullSafeArray(this.attributes.getCacheListeners(), CacheListener.class)
: new CacheListener[0]);
}
/* (non-Javadoc) */
private ClientRegionFactory<K, V> setDiskStoreName(ClientRegionFactory<K, V> clientRegionFactory) {
if (StringUtils.hasText(this.diskStoreName)) {
clientRegionFactory.setDiskStoreName(this.diskStoreName);
}
return clientRegionFactory;
}
/* (non-Javadoc) */
private ClientRegionFactory<K, V> setEvictionAttributes(ClientRegionFactory<K, V> clientRegionFactory) {
if (this.evictionAttributes != null) {
clientRegionFactory.setEvictionAttributes(this.evictionAttributes);
}
return clientRegionFactory;
}
/* (non-Javadoc) */
private ClientRegionFactory<K, V> setPoolName(ClientRegionFactory<K, V> clientRegionFactory) {
String poolName = resolvePoolName();
if (StringUtils.hasText(poolName)) {
clientRegionFactory.setPoolName(eagerlyInitializePool(poolName));
}
return clientRegionFactory;
}
/* (non-Javadoc) */
private String resolvePoolName() {
String poolName = this.poolName;
if (!StringUtils.hasText(poolName)) {
String defaultPoolName = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME;
poolName = (this.beanFactory.containsBean(defaultPoolName) ? defaultPoolName : poolName);
}
return poolName;
}
/* (non-Javadoc) */
private String eagerlyInitializePool(String poolName) {
try {
if (this.beanFactory.isTypeMatch(poolName, Pool.class)) {
if (log.isDebugEnabled()) {
log.debug(String.format("Found bean definition for Pool [%1$s]; Eagerly initializing...", poolName));
}
this.beanFactory.getBean(poolName, Pool.class);
}
}
catch (BeansException ignore) {
log.warn(ignore.getMessage());
}
if (!ObjectUtils.isEmpty(cacheListeners)) {
for (CacheListener<K, V> cacheListener : cacheListeners) {
factory.addCacheListener(cacheListener);
}
}
return poolName;
}
protected void postProcess(final Region<K, V> region) {
/* (non-Javadoc) */
protected void postProcess(Region<K, V> region) throws Exception {
loadSnapshot(region);
registerInterests(region);
setCacheLoader(region);
setCacheWriter(region);
}
private void registerInterests(final Region<K, V> region) {
/* (non-Javadoc) */
private Region<K, V> loadSnapshot(Region<K, V> region) throws Exception {
if (snapshot != null) {
region.loadSnapshot(snapshot.getInputStream());
}
return region;
}
/* (non-Javadoc) */
private Region<K, V> registerInterests(Region<K, V> region) {
if (!ObjectUtils.isEmpty(interests)) {
for (Interest<K> interest : interests) {
if (interest instanceof RegexInterest) {
@@ -314,20 +376,31 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
}
}
}
return region;
}
private void setCacheLoader(final Region<K, V> region) {
/* (non-Javadoc) */
private Region<K, V> setCacheLoader(Region<K, V> region) {
if (cacheLoader != null) {
region.getAttributesMutator().setCacheLoader(this.cacheLoader);
}
return region;
}
private void setCacheWriter(final Region<K, V> region) {
/* (non-Javadoc) */
private Region<K, V> setCacheWriter(Region<K, V> region) {
if (cacheWriter != null) {
region.getAttributesMutator().setCacheWriter(this.cacheWriter);
}
return region;
}
/**
* @inheritDoc
*/
@Override
public void destroy() throws Exception {
Region<K, V> region = getObject();
@@ -368,43 +441,7 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
this.beanFactory = beanFactory;
}
/**
* Set the interests for this client region. Both key and regex interest are
* supported.
*
* @param interests the interests to set
*/
public void setInterests(Interest<K>[] interests) {
this.interests = interests;
}
/**
* @return the interests
*/
Interest<K>[] getInterests() {
return interests;
}
/**
* Sets the pool used by this client.
*
* @param pool the GemFire client pool.
*/
public void setPool(Pool pool) {
Assert.notNull(pool, "pool cannot be null");
setPoolName(pool.getName());
}
/**
* Sets the pool name used by this client.
*
* @param poolName a String specify the name of the GemFire client pool.
*/
public void setPoolName(String poolName) {
Assert.hasText(poolName, "pool name is required");
this.poolName = poolName;
}
/* (non-Javadoc) */
final boolean isClose() {
return close;
}
@@ -422,23 +459,6 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
this.destroy = (this.destroy && !close); // retain previous value iff close is false.
}
final boolean isDestroy() {
return destroy;
}
/**
* Indicates whether the region referred by this factory bean will be
* destroyed on shutdown (default false). Note: destroy and close are
* mutually exclusive. Enabling one will automatically disable the other.
*
* @param destroy whether or not to destroy the region
* @see #setClose(boolean)
*/
public void setDestroy(boolean destroy) {
this.destroy = destroy;
this.close = (this.close && !destroy); // retain previous value iff destroy is false;
}
/**
* Sets the cache listeners used for the region used by this factory. Used
* only when a new region is created.Overrides the settings specified
@@ -495,6 +515,24 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
setDataPolicy(resolvedDataPolicy);
}
/* (non-Javadoc) */
final boolean isDestroy() {
return destroy;
}
/**
* Indicates whether the region referred by this factory bean will be
* destroyed on shutdown (default false). Note: destroy and close are
* mutually exclusive. Enabling one will automatically disable the other.
*
* @param destroy whether or not to destroy the region
* @see #setClose(boolean)
*/
public void setDestroy(boolean destroy) {
this.destroy = destroy;
this.close = (this.close && !destroy); // retain previous value iff destroy is false;
}
/**
* Sets the name of disk store to use for overflow and persistence
*
@@ -504,6 +542,25 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
this.diskStoreName = diskStoreName;
}
public void setEvictionAttributes(EvictionAttributes evictionAttributes) {
this.evictionAttributes = evictionAttributes;
}
/**
* Set the interests for this client region. Both key and regex interest are
* supported.
*
* @param interests the interests to set
*/
public void setInterests(Interest<K>[] interests) {
this.interests = interests;
}
/* (non-Javadoc) */
Interest<K>[] getInterests() {
return this.interests;
}
protected boolean isPersistentUnspecified() {
return (persistent == null);
}
@@ -521,26 +578,44 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
}
/**
* Initializes the client using a GemFire {@link ClientRegionShortcut}. The
* recommended way for creating clients since it covers all the major
* scenarios with minimal configuration.
* Sets the {@link Pool} used by this client {@link Region}.
*
* @param shortcut the ClientRegionShortcut to use.
* @param pool GemFire client {@link Pool}.
* @see com.gemstone.gemfire.cache.client.Pool
*/
public void setPool(Pool pool) {
Assert.notNull(pool, "Pool cannot be null");
setPoolName(pool.getName());
}
/**
* Sets the {@link Pool} name used by this client {@link Region}.
*
* @param poolName String specifying the name of the GemFire client {@link Pool}.
*/
public void setPoolName(String poolName) {
Assert.hasText(poolName, "Pool name is required");
this.poolName = poolName;
}
/**
* Initializes the client {@link Region} using a GemFire {@link ClientRegionShortcut}.
*
* @param shortcut {@link ClientRegionShortcut} used to initialize this client {@link Region}.
*/
public void setShortcut(ClientRegionShortcut shortcut) {
this.shortcut = shortcut;
}
/**
* Sets the snapshots used for loading a newly <i>created</i> region. That
* is, the snapshot will be used <i>only</i> when a new region is created -
* if the region already exists, no loading will be performed.
* Specifies the data snapshots used for loading a newly <i>created</i> {@link Region}.
* The snapshot will be used <i>only</i> when a new {@link Region} is created.
* If the {@link Region} already exists, no loading will be performed.
*
* @param snapshot the snapshot to set
* @see #setName(String)
* @param snapshot {@link Resource} referencing the snapshot used to load the {@link Region} with data.
* @see org.springframework.core.io.Resource
*/
public void setSnapshot(Resource snapshot) {
this.snapshot = snapshot;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.data.gemfire.config.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.gemstone.gemfire.cache.EvictionAttributes;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.util.ObjectSizer;
import org.springframework.context.annotation.Import;
import org.springframework.data.gemfire.EvictionActionType;
import org.springframework.data.gemfire.EvictionPolicyType;
/**
* The {@link EnableEviction} annotation marks a Spring {@link org.springframework.context.annotation.Configuration @Configuration}
* annotated class to enable {@link Region} Eviction.
*
* @author John Blum
* @see org.springframework.context.annotation.Import
* @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration
* @see com.gemstone.gemfire.cache.Region
* @since 1.9.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(EvictionConfiguration.class)
@SuppressWarnings({ "unused" })
public @interface EnableEviction {
/**
* Defines individual {@link Region} Eviction policies or customizes the default Eviction policy applied
* to all {@link Region Regions}.
*
* Defaults to empty.
*/
EvictionPolicy[] policies() default {};
/**
* Definition for a specific Eviction policy that can be applied to 1 or more {@link Region Regions}.
*
* An Eviction policy defines the maximum (a.k.a. threshold) along with {@link ObjectSizer} used to size
* {@link Region} entry values and the action applied when {@link Region} entries are to be evicted.
*
* Additionally, the Eviction policy defines the algorithm used (eviction based on entry count, JVM Heap percentage
* or system memory size used) to determine when an Eviction should occur.
*/
@interface EvictionPolicy {
/**
* Action to take on an {@link Region} entry when evicted.
*
* Defaults to {@link EvictionActionType#LOCAL_DESTROY}.
*
* @see org.springframework.data.gemfire.EvictionActionType
*/
EvictionActionType action() default EvictionActionType.LOCAL_DESTROY;
/**
* Threshold applied for entry count Eviction.
*
* Defaults to {@link EvictionAttributes#DEFAULT_ENTRIES_MAXIMUM}
*/
int maximum() default EvictionAttributes.DEFAULT_ENTRIES_MAXIMUM;
/**
* Name of a Spring bean of type {@link ObjectSizer} defined in the Spring context used
* to size {@link Region} entry values.
*
* Defaults to empty.
*
* @see com.gemstone.gemfire.cache.util.ObjectSizer
*/
String objectSizerName() default "";
/**
* Names of {@link Region Regions} for which this Eviction policy applies.
*
* Defaults to empty.
*/
String[] regionNames() default {};
/**
* Eviction alorithm used during Eviction.
*
* Defaults to {@link EvictionPolicyType#ENTRY_COUNT}.
*
* @see org.springframework.data.gemfire.EvictionPolicyType
*/
EvictionPolicyType type() default EvictionPolicyType.ENTRY_COUNT;
}
}

View File

@@ -0,0 +1,466 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.data.gemfire.config.annotation;
import static org.springframework.data.gemfire.config.annotation.EnableEviction.EvictionPolicy;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.gemstone.gemfire.cache.EvictionAttributes;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.util.ObjectSizer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.EvictionActionType;
import org.springframework.data.gemfire.EvictionAttributesFactoryBean;
import org.springframework.data.gemfire.EvictionPolicyType;
import org.springframework.data.gemfire.RegionFactoryBean;
import org.springframework.data.gemfire.RegionLookupFactoryBean;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* The {@link EvictionConfiguration} class is a Spring {@link Configuration @Configuration} annotated class to enable
* Eviction policy configuration on GemFire/Geode {@link Region Regions}.
*
* @author John Blum
* @see org.springframework.beans.factory.config.BeanPostProcessor
* @see org.springframework.context.ApplicationContext
* @see org.springframework.context.ApplicationContextAware
* @see org.springframework.context.annotation.Bean
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.context.annotation.ImportAware
* @see org.springframework.data.gemfire.EvictionActionType
* @see org.springframework.data.gemfire.EvictionAttributesFactoryBean
* @see org.springframework.data.gemfire.EvictionPolicyType
* @see org.springframework.data.gemfire.RegionFactoryBean
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
* @see com.gemstone.gemfire.cache.EvictionAttributes
* @see com.gemstone.gemfire.cache.Region
* @since 1.9.0
*/
@Configuration
public class EvictionConfiguration implements ApplicationContextAware, ImportAware {
private ApplicationContext applicationContext;
private EvictionPolicyConfigurer evictionPolicyConfigurer;
/**
* Determines whether the Spring bean is an instance of {@link RegionFactoryBean}
* or {@link ClientRegionFactoryBean}.
*
* @param bean Spring bean to evaluate.
* @return a boolean value indicating whether the Spring bean is an instance of {@link RegionFactoryBean}.
* @see org.springframework.data.gemfire.RegionFactoryBean
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
*/
protected static boolean isRegionFactoryBean(Object bean) {
return (bean instanceof RegionFactoryBean || bean instanceof ClientRegionFactoryBean);
}
/**
* Returns the {@link Annotation} {@link Class type} that enables and configures Eviction.
*
* @return the {@link Annotation} {@link Class type} to enable and configure Eviction.
* @see java.lang.annotation.Annotation
* @see java.lang.Class
*/
protected Class<? extends Annotation> getAnnotationType() {
return EnableEviction.class;
}
/**
* Returns the name of the {@link Annotation} type that enables and configures Eviction.
*
* @return the name of the {@link Annotation} type that enables and configures Eviction.
* @see java.lang.Class#getName()
* @see #getAnnotationType()
*/
protected String getAnnotationTypeName() {
return getAnnotationType().getName();
}
/**
* Returns the simple name of the {@link Annotation} type that enables and configures Eviction.
*
* @return the simple name of the {@link Annotation} type that enables and configures Eviction.
* @see java.lang.Class#getSimpleName()
* @see #getAnnotationType()
*/
@SuppressWarnings("unused")
protected String getAnnotationTypeSimpleName() {
return getAnnotationType().getSimpleName();
}
/**
* @inheritDoc
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* @inheritDoc
*/
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
if (importMetadata.hasAnnotation(getAnnotationTypeName())) {
Map<String, Object> enableEvictionAttributes =
importMetadata.getAnnotationAttributes(getAnnotationTypeName());
AnnotationAttributes[] policies = (AnnotationAttributes[]) enableEvictionAttributes.get("policies");
for (AnnotationAttributes evictionPolicyAttributes
: ArrayUtils.nullSafeArray(policies, AnnotationAttributes.class)) {
this.evictionPolicyConfigurer = ComposableEvictionPolicyConfigurer.compose(
this.evictionPolicyConfigurer, EvictionPolicyMetaData.from(evictionPolicyAttributes,
this.applicationContext));
}
this.evictionPolicyConfigurer = (this.evictionPolicyConfigurer != null ? this.evictionPolicyConfigurer
: EvictionPolicyMetaData.fromDefaults());
}
}
/**
* Returns a reference to the configured {@link EvictionPolicyConfigurer} used to configure the Eviction policy
* of a {@link Region}.
*
* @return a reference to the configured {@link EvictionPolicyConfigurer}.
* @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration.EvictionPolicyConfigurer
*/
protected EvictionPolicyConfigurer getEvictionPolicyConfigurer() {
Assert.state(this.evictionPolicyConfigurer != null,
"EvictionPolicyConfigurer was not properly configured and initialized");
return this.evictionPolicyConfigurer;
}
@Bean
@SuppressWarnings("unused")
public BeanPostProcessor evictionBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return (isRegionFactoryBean(bean) ? getEvictionPolicyConfigurer().configure(bean) : bean);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
};
}
/**
* {@link EvictionPolicyConfigurer} configures the Eviction policy of a GemFire {@link Region}.
*/
protected interface EvictionPolicyConfigurer {
/**
* Configure the Eviction policy on the given SDG {@link RegionFactoryBean} or {@link ClientRegionFactoryBean}
* used to create a GemFire {@link Region}.
*
* @param regionFactoryBean {@link RegionFactoryBean} or {@link ClientRegionFactoryBean} used to create
* a GemFire {@link Region}.
* @return the given {@code regionFactoryBean}.
* @see org.springframework.data.gemfire.RegionFactoryBean
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean
*/
Object configure(Object regionFactoryBean);
}
/**
* {@link ComposableEvictionPolicyConfigurer} is a {@link EvictionPolicyConfigurer} implementation that composes
* multiple {@link EvictionPolicyConfigurer} objects into a composition using the Composite Software Design Pattern
* making the composition appear as a single {@link EvictionPolicyConfigurer}.
*
* @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration.EvictionPolicyConfigurer
*/
protected static class ComposableEvictionPolicyConfigurer implements EvictionPolicyConfigurer {
private final EvictionPolicyConfigurer one;
private final EvictionPolicyConfigurer two;
/**
* Composes the array of {@link EvictionPolicyConfigurer} objects into a single
* {@link EvictionPolicyConfigurer} implementation using the Composite Software Design Pattern.
*
* @param array array of {@link EvictionPolicyConfigurer} objects to compose.
* @return an {@link EvictionPolicyConfigurer} implementation composed from the array
* of {@link EvictionPolicyConfigurer} objects.
* @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration.EvictionPolicyConfigurer
* @see #compose(Iterable)
*/
@SuppressWarnings("unused")
protected static EvictionPolicyConfigurer compose(EvictionPolicyConfigurer[] array) {
return compose(Arrays.asList(ArrayUtils.nullSafeArray(array, EvictionPolicyConfigurer.class)));
}
/**
* Composes the {@link Iterable} of {@link EvictionPolicyConfigurer} objects into a single
* {@link EvictionPolicyConfigurer} implementation using the Composite Software Design Pattern.
*
* @param iterable {@link Iterable} of {@link EvictionPolicyConfigurer} objects to compose.
* @return an {@link EvictionPolicyConfigurer} implementation composed from the {@link Iterable}
* of {@link EvictionPolicyConfigurer} objects.
* @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration.EvictionPolicyConfigurer
* @see #compose(EvictionPolicyConfigurer, EvictionPolicyConfigurer)
*/
protected static EvictionPolicyConfigurer compose(Iterable<EvictionPolicyConfigurer> iterable) {
EvictionPolicyConfigurer current = null;
for (EvictionPolicyConfigurer evictionPolicyConfigurer : CollectionUtils.nullSafeIterable(iterable)) {
current = compose(current, evictionPolicyConfigurer);
}
return current;
}
/**
* Composes two {@link EvictionPolicyConfigurer} objects into a composition object
* implementing the {@link EvictionPolicyConfigurer} interface.
*
* @param one first {@link EvictionPolicyConfigurer} object to compose.
* @param two second {@link EvictionPolicyConfigurer} object to compose.
* @return an {@link EvictionPolicyConfigurer} object implementation composed of
* multiple {@link EvictionPolicyConfigurer} objects using the Composite Software Design Pattern.
*/
protected static EvictionPolicyConfigurer compose(EvictionPolicyConfigurer one, EvictionPolicyConfigurer two) {
return (one == null ? two : (two == null ? one : new ComposableEvictionPolicyConfigurer(one, two)));
}
/**
* Constructs a new instance of the {@link ComposableEvictionPolicyConfigurer} initialized with the two
* {@link EvictionPolicyConfigurer} objects.
*
* @param one first {@link EvictionPolicyConfigurer} object to compose.
* @param two second {@link EvictionPolicyConfigurer} object to compose.
*/
private ComposableEvictionPolicyConfigurer(EvictionPolicyConfigurer one, EvictionPolicyConfigurer two) {
this.one = one;
this.two = two;
}
/**
* @inheritDoc
*/
@Override
public Object configure(Object regionFactoryBean) {
return two.configure(one.configure(regionFactoryBean));
}
}
protected static class EvictionPolicyMetaData implements EvictionPolicyConfigurer {
protected static final String[] ALL_REGIONS = new String[0];
private final EvictionAttributes evictionAttributes;
private final Set<String> regionNames = new HashSet<String>();
protected static EvictionPolicyMetaData from(AnnotationAttributes evictionPolicyAttributes,
ApplicationContext applicationContext) {
return from((Integer) evictionPolicyAttributes.get("maximum"),
evictionPolicyAttributes.<EvictionPolicyType>getEnum("type"),
evictionPolicyAttributes.<EvictionActionType>getEnum("action"),
resolveObjectSizer(evictionPolicyAttributes.getString("objectSizerName"), applicationContext),
evictionPolicyAttributes.getStringArray("regionNames"));
}
protected static EvictionPolicyMetaData from(EvictionPolicy evictionPolicy,
ApplicationContext applicationContext) {
return from(evictionPolicy.maximum(), evictionPolicy.type(), evictionPolicy.action(),
resolveObjectSizer(evictionPolicy.objectSizerName(), applicationContext), evictionPolicy.regionNames());
}
protected static EvictionPolicyMetaData from(int maximum, EvictionPolicyType type, EvictionActionType action,
ObjectSizer objectSizer, String... regionNames) {
EvictionAttributesFactoryBean factoryBean = new EvictionAttributesFactoryBean();
factoryBean.setAction(action.getEvictionAction());
factoryBean.setObjectSizer(objectSizer);
factoryBean.setThreshold(resolveThreshold(maximum, type));
factoryBean.setType(type);
factoryBean.afterPropertiesSet();
return new EvictionPolicyMetaData(factoryBean.getObject(), regionNames);
}
protected static EvictionPolicyMetaData fromDefaults() {
return new EvictionPolicyMetaData(EvictionAttributes.createLRUEntryAttributes());
}
protected static ObjectSizer resolveObjectSizer(String objectSizerName, ApplicationContext applicationContext) {
boolean resolvable = StringUtils.hasText(objectSizerName)
&& applicationContext.containsBean(objectSizerName);
return (resolvable ? applicationContext.getBean(objectSizerName, ObjectSizer.class) : null);
}
/**
* Resolves the Eviction policy threshold (a.k.a. maximum) based on the {@link EvictionPolicyType}.
*
* For instance {@link EvictionPolicyType#HEAP_PERCENTAGE} does not support maximum/threshold since
* the settings are determined by the GemFire/Geode cache critical heap percentage and eviction heap percentage
* System property settings.
*
* @param maximum integer value specifying the configured Eviction threshold.
* @param type {@link EvictionPolicyType} specifying the type of Eviction algorithm.
* @return a resolved value for the Eviction maximum/threshold.
* @see org.springframework.data.gemfire.EvictionPolicyType
*/
protected static Integer resolveThreshold(int maximum, EvictionPolicyType type) {
return (EvictionPolicyType.HEAP_PERCENTAGE.equals(type) ? null : maximum);
}
/**
* Constructs an instance of {@link EvictionPolicyMetaData} initialized with the given
* {@link EvictionAttributes} applying to all {@link Region Regions}.
*
* @param evictionAttributes {@link EvictionAttributes} specifying the Eviction policy configuration
* for a {@link Region}.
* @see com.gemstone.gemfire.cache.EvictionAttributes
* @see #EvictionPolicyMetaData(EvictionAttributes, String[])
*/
protected EvictionPolicyMetaData(EvictionAttributes evictionAttributes) {
this(evictionAttributes, ALL_REGIONS);
}
/**
* Constructs an instance of {@link EvictionPolicyMetaData} initialized with the given
* {@link EvictionAttributes} to apply to the specific {@link Region Regions}.
*
* @param evictionAttributes {@link EvictionAttributes} specifying the Eviction policy configuration
* for a {@link Region}.
* @param regionNames names of {@link Region Regions} on which the Eviction policy is applied.
* @see com.gemstone.gemfire.cache.EvictionAttributes
*/
protected EvictionPolicyMetaData(EvictionAttributes evictionAttributes, String[] regionNames) {
Assert.notNull(evictionAttributes, "EvictionAttributes must not be null");
this.evictionAttributes = evictionAttributes;
Collections.addAll(this.regionNames, ArrayUtils.nullSafeArray(regionNames, String.class));
}
/**
* Returns an instance of the {@link EvictionAttributes} specifying the Eviction policy configuration
* captured in this Eviction policy meta-data.
*
* @return an instance of the {@link EvictionAttributes} specifying the {@link Region}
* Eviction policy configuration.
* @throws IllegalStateException if the {@link EvictionAttributes} were not properly initialized.
* @see com.gemstone.gemfire.cache.EvictionAttributes
*/
protected EvictionAttributes getEvictionAttributes() {
Assert.state(this.evictionAttributes != null,
"EvictionAttributes was not properly configured and initialized");
return this.evictionAttributes;
}
/**
* Determines whether the given {@link Object} (e.g. Spring bean) is accepted for Eviction policy configuration.
*
* @param regionFactoryBean {@link Object} being evaluated as an Eviction policy configuration candidate.
* @return a boolean value indicating whether the {@link Object} is accepted for Eviction policy configuration.
* @see #isRegionFactoryBean(Object)
* @see #resolveRegionName(Object)
* @see #accepts(String)
*/
protected boolean accepts(Object regionFactoryBean) {
return (isRegionFactoryBean(regionFactoryBean) && accepts(resolveRegionName(regionFactoryBean)));
}
/**
* Determine whether the {@link Region} identified by name is accepted for Eviction policy configuration.
*
* @param regionName name of the {@link Region} targeted for Eviction policy configuration.
* @return a boolean value if the named {@link Region} is accepted for Eviction policy configuration.
*/
protected boolean accepts(String regionName) {
return (this.regionNames.isEmpty() || this.regionNames.contains(regionName));
}
/**
* Resolves the name of a given {@link Region} from the corresponding {@link RegionLookupFactoryBean} object.
*
* @param regionFactoryBean {@link RegionLookupFactoryBean} from which to resolve the {@link Region} name.
* @return the resolved name of the {@link Region} created from the given {@link RegionLookupFactoryBean}.
* @see org.springframework.data.gemfire.RegionLookupFactoryBean#resolveRegionName()
*/
protected String resolveRegionName(Object regionFactoryBean) {
return (regionFactoryBean instanceof RegionLookupFactoryBean
? ((RegionLookupFactoryBean) regionFactoryBean).resolveRegionName() : null);
}
/**
* Sets the {@link EvictionAttributes} on the {@link RegionFactoryBean} or {@link ClientRegionFactoryBean}
* used to create the targeted {@link Region}.
*
* @param regionFactoryBean {@link RegionFactoryBean} or {@link ClientRegionFactoryBean} on which to
* set the {@link EvictionAttributes} encapsulating the Eviction policy for the targeted {@link Region}.
* @return the {@code regionFactoryBean}.
* @see org.springframework.data.gemfire.RegionFactoryBean#setEvictionAttributes(EvictionAttributes)
* @see org.springframework.data.gemfire.client.ClientRegionFactoryBean#setEvictionAttributes(EvictionAttributes)
* @see com.gemstone.gemfire.cache.EvictionAttributes
* @see #getEvictionAttributes()
*/
protected Object setEvictionAttributes(Object regionFactoryBean) {
if (regionFactoryBean instanceof RegionFactoryBean) {
((RegionFactoryBean) regionFactoryBean).setEvictionAttributes(getEvictionAttributes());
}
else {
((ClientRegionFactoryBean) regionFactoryBean).setEvictionAttributes(getEvictionAttributes());
}
return regionFactoryBean;
}
/**
* @inheritDoc
*/
@Override
public Object configure(Object regionFactoryBean) {
return (accepts(regionFactoryBean) ? setEvictionAttributes(regionFactoryBean) : regionFactoryBean);
}
}
}

View File

@@ -28,6 +28,10 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.Scope;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
@@ -52,10 +56,6 @@ import org.springframework.data.gemfire.test.support.ZipUtils;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.Scope;
/**
* The CacheClusterConfigurationIntegrationTest class is a test suite of test cases testing the integration of
* Spring Data GemFire with GemFire 8's new shared, persistent, cluster configuration service.
@@ -82,15 +82,15 @@ public class CacheClusterConfigurationIntegrationTest {
@Rule
public TestRule watchman = new TestWatcher() {
@Override
protected void failed(final Throwable t, final Description description) {
protected void failed(Throwable throwable, Description description) {
System.err.println(String.format("Test '%1$s' failed...", description.getDisplayName()));
System.err.println(ThrowableUtils.toString(t));
System.err.println(ThrowableUtils.toString(throwable));
System.err.println("Locator process log file contents were...");
System.err.println(getLocatorProcessOutput(description));
}
@Override
protected void finished(final Description description) {
protected void finished(Description description) {
if (Boolean.valueOf(System.getProperty("spring.gemfire.fork.clean", Boolean.TRUE.toString()))) {
try {
FileUtils.write(new File(locatorWorkingDirectory.getParent(),
@@ -103,7 +103,7 @@ public class CacheClusterConfigurationIntegrationTest {
}
}
private String getLocatorProcessOutput(final Description description) {
private String getLocatorProcessOutput(Description description) {
try {
String locatorProcessOutputString = StringUtils.collectionToDelimitedString(locatorProcessOutput,
FileUtils.LINE_SEPARATOR, String.format("[%1$s] - ", description.getMethodName()), "");
@@ -132,6 +132,8 @@ public class CacheClusterConfigurationIntegrationTest {
List<String> arguments = new ArrayList<String>();
arguments.add("-Dgemfire.name=" + locatorName);
arguments.add("-Dgemfire.mcast-port=0");
arguments.add("-Dgemfire.log-level=error");
arguments.add("-Dspring.gemfire.enable-cluster-configuration=true");
arguments.add("-Dspring.gemfire.load-cluster-configuration=true");
@@ -192,11 +194,11 @@ public class CacheClusterConfigurationIntegrationTest {
return baseLocation.concat(File.separator).concat(configLocation);
}
protected Region getRegion(final ConfigurableApplicationContext applicationContext, final String regionBeanName) {
protected Region getRegion(ConfigurableApplicationContext applicationContext, String regionBeanName) {
return applicationContext.getBean(regionBeanName, Region.class);
}
protected ConfigurableApplicationContext newApplicationContext(final String... configLocations) {
protected ConfigurableApplicationContext newApplicationContext(String... configLocations) {
ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocations);
applicationContext.registerShutdownHook();
return applicationContext;
@@ -205,7 +207,7 @@ public class CacheClusterConfigurationIntegrationTest {
@Test
@Ignore
// TODO re-enable the test once the GemFire Cluster Configuration Service race condition has been properly fixed!
public void testClusterConfiguration() {
public void clusterConfigurationTest() {
ConfigurableApplicationContext applicationContext = newApplicationContext(
getLocation("cacheUsingClusterConfigurationIntegrationTest.xml"));
@@ -226,17 +228,18 @@ public class CacheClusterConfigurationIntegrationTest {
}
@Test
public void testLocalOnlyConfiguration() {
public void localConfigurationTest() {
try {
newApplicationContext(getLocation("cacheUsingLocalOnlyConfigurationIntegrationTest.xml"));
fail("Loading the 'cacheUsingLocalOnlyConfigurationIntegrationTest.xml' Spring ApplicationContext"
+ " configuration file should have resulted in an Exception due to the Region lookup on"
+ " 'ClusterConfigRegion' when GemFire Cluster Configuration is disabled!");
}
catch (BeanCreationException expected) {
assertTrue(expected.getCause() instanceof BeanInitializationException);
assertTrue(expected.getCause().getMessage().contains("Cannot find Region [ClusterConfigRegion] in Cache"));
assertTrue(expected.getCause().getMessage().matches(
"Region \\[ClusterConfigRegion\\] in Cache \\[.*\\] not found"));
}
}
}

View File

@@ -15,9 +15,9 @@
*/
package org.springframework.data.gemfire.client;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -37,14 +37,6 @@ import static org.mockito.Mockito.when;
import java.io.InputStream;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.io.Resource;
import org.springframework.data.gemfire.TestUtils;
import org.springframework.data.gemfire.config.xml.GemfireConstants;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.EvictionAttributes;
import com.gemstone.gemfire.cache.ExpirationAttributes;
@@ -57,6 +49,14 @@ import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
import com.gemstone.gemfire.cache.client.Pool;
import com.gemstone.gemfire.compression.Compressor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.io.Resource;
import org.springframework.data.gemfire.TestUtils;
import org.springframework.data.gemfire.config.xml.GemfireConstants;
/**
* @author David Turanski
* @author John Blum
@@ -79,7 +79,7 @@ public class ClientRegionFactoryBeanTest {
@Test
@SuppressWarnings({ "deprecation", "unchecked" })
public void testLookupFallbackUsingDefaultShortcut() throws Exception {
final String testRegionName = "TestRegion";
String testRegionName = "TestRegion";
ClientCache mockClientCache = mock(ClientCache.class);
ClientRegionFactory mockClientRegionFactory = mock(ClientRegionFactory.class);
@@ -119,9 +119,12 @@ public class ClientRegionFactoryBeanTest {
when(mockPool.getName()).thenReturn("TestPoolTwo");
when(mockSnapshot.getInputStream()).thenReturn(mock(InputStream.class));
EvictionAttributes evictionAttributes = EvictionAttributes.createLRUEntryAttributes();
factoryBean.setAttributes(mockRegionAttributes);
factoryBean.setBeanFactory(mockBeanFactory);
factoryBean.setDiskStoreName("TestDiskStoreTwo");
factoryBean.setEvictionAttributes(evictionAttributes);
factoryBean.setPersistent(false);
factoryBean.setPoolName("TestPoolTwo");
factoryBean.setSnapshot(mockSnapshot);
@@ -142,7 +145,7 @@ public class ClientRegionFactoryBeanTest {
verify(mockClientRegionFactory, times(1)).setDiskSynchronous(eq(false));
verify(mockClientRegionFactory, times(1)).setEntryIdleTimeout(any(ExpirationAttributes.class));
verify(mockClientRegionFactory, times(1)).setEntryTimeToLive(any(ExpirationAttributes.class));
verify(mockClientRegionFactory, times(1)).setEvictionAttributes(any(EvictionAttributes.class));
verify(mockClientRegionFactory, times(1)).setEvictionAttributes(eq(evictionAttributes));
verify(mockClientRegionFactory, times(1)).setInitialCapacity(eq(101));
verify(mockClientRegionFactory, times(1)).setKeyConstraint(eq(Long.class));
verify(mockClientRegionFactory, times(1)).setLoadFactor(eq(0.75f));
@@ -154,7 +157,7 @@ public class ClientRegionFactoryBeanTest {
verify(mockClientRegionFactory, times(1)).setPoolName(eq("TestPoolTwo"));
verify(mockClientRegionFactory, times(1)).setDiskStoreName(eq("TestDiskStoreTwo"));
verify(mockClientRegionFactory, times(1)).create(eq(testRegionName));
verify(mockRegion, times(1)).loadSnapshot(any(InputStream.class));
verify(mockRegion, never()).loadSnapshot(any(InputStream.class));
}
@Test
@@ -430,7 +433,7 @@ public class ClientRegionFactoryBeanTest {
factoryBean.resolveClientRegionShortcut();
}
catch (IllegalArgumentException expected) {
assertEquals("Client Region Shortcut 'CACHING_PROXY' is invalid when persistent is true.",
assertEquals("Client Region Shortcut 'CACHING_PROXY' is invalid when persistent is true",
expected.getMessage());
throw expected;
}
@@ -457,7 +460,7 @@ public class ClientRegionFactoryBeanTest {
factoryBean.resolveClientRegionShortcut();
}
catch (IllegalArgumentException expected) {
assertEquals("Client Region Shortcut 'LOCAL_PERSISTENT' is invalid when persistent is false.",
assertEquals("Client Region Shortcut 'LOCAL_PERSISTENT' is invalid when persistent is false",
expected.getMessage());
throw expected;
}
@@ -504,7 +507,7 @@ public class ClientRegionFactoryBeanTest {
factoryBean.resolveClientRegionShortcut();
}
catch (IllegalArgumentException expected) {
assertEquals("Data Policy 'NORMAL' is invalid when persistent is true.", expected.getMessage());
assertEquals("Data Policy 'NORMAL' is invalid when persistent is true", expected.getMessage());
throw expected;
}
}
@@ -530,7 +533,7 @@ public class ClientRegionFactoryBeanTest {
factoryBean.resolveClientRegionShortcut();
}
catch (IllegalArgumentException expected) {
assertEquals("Data Policy 'PERSISTENT_REPLICATE' is invalid when persistent is false.", expected.getMessage());
assertEquals("Data Policy 'PERSISTENT_REPLICATE' is invalid when persistent is false", expected.getMessage());
throw expected;
}
}
@@ -686,5 +689,4 @@ public class ClientRegionFactoryBeanTest {
clientRegionFactoryBean.destroy();
}
}

View File

@@ -0,0 +1,277 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.data.gemfire.config.annotation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.data.gemfire.config.annotation.EnableEviction.EvictionPolicy;
import java.util.concurrent.atomic.AtomicReference;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.EvictionAttributes;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.RegionAttributes;
import com.gemstone.gemfire.cache.RegionFactory;
import com.gemstone.gemfire.cache.util.ObjectSizer;
import org.junit.After;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.EvictionActionType;
import org.springframework.data.gemfire.EvictionAttributesFactoryBean;
import org.springframework.data.gemfire.EvictionPolicyType;
import org.springframework.data.gemfire.PartitionedRegionFactoryBean;
import org.springframework.data.gemfire.ReplicatedRegionFactoryBean;
import org.springframework.data.gemfire.util.ArrayUtils;
/**
* Unit tests for the {@link EnableEviction} annotation and {@link EvictionConfiguration} class.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mockito
* @see org.springframework.data.gemfire.config.annotation.EnableEviction
* @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration
* @see org.springframework.data.gemfire.EvictionAttributesFactoryBean
* @see com.gemstone.gemfire.cache.Region
* @see com.gemstone.gemfire.cache.EvictionAttributes
* @since 1.9.0
*/
public class EnableEvictionConfigurationUnitTests {
private ConfigurableApplicationContext applicationContext;
@After
public void tearDown() {
if (applicationContext != null) {
applicationContext.close();
}
}
protected void assertEvictionAttributes(Region region, EvictionAttributes expectedEvictionAttributes) {
assertThat(region).isNotNull();
assertThat(region.getAttributes()).isNotNull();
assertEvictionAttributes(region.getAttributes().getEvictionAttributes(), expectedEvictionAttributes);
}
protected void assertEvictionAttributes(EvictionAttributes actualEvictionAttributes,
EvictionAttributes expectedEvictionAttributes) {
assertThat(actualEvictionAttributes).isNotNull();
assertThat(actualEvictionAttributes.getAction()).isEqualTo(expectedEvictionAttributes.getAction());
assertThat(actualEvictionAttributes.getAlgorithm()).isEqualTo(expectedEvictionAttributes.getAlgorithm());
assertThat(actualEvictionAttributes.getObjectSizer()).isEqualTo(expectedEvictionAttributes.getObjectSizer());
if (!EvictionPolicyType.HEAP_PERCENTAGE.equals(
EvictionPolicyType.valueOf(actualEvictionAttributes.getAlgorithm()))) {
assertThat(actualEvictionAttributes.getMaximum()).isEqualTo(expectedEvictionAttributes.getMaximum());
}
}
@SuppressWarnings("unchecked")
protected <K, V> Region<K, V> getRegion(String beanName) {
return applicationContext.getBean(beanName, Region.class);
}
protected AnnotationConfigApplicationContext newApplicationContext(Class<?>... annotatedClasses) {
return new AnnotationConfigApplicationContext(annotatedClasses);
}
protected EvictionAttributes newEvictionAttributes(Integer maximum, EvictionPolicyType type, EvictionActionType action,
ObjectSizer... objectSizer) {
EvictionAttributesFactoryBean evictionAttributesFactory = new EvictionAttributesFactoryBean();
evictionAttributesFactory.setAction(action.getEvictionAction());
evictionAttributesFactory.setObjectSizer(ArrayUtils.getFirst(objectSizer));
evictionAttributesFactory.setThreshold(maximum);
evictionAttributesFactory.setType(type);
evictionAttributesFactory.afterPropertiesSet();
return evictionAttributesFactory.getObject();
}
@Test
public void usesDefaultEvictionPolicyConfiguration() {
applicationContext = newApplicationContext(DefaultEvictionPolicyConfiguration.class);
EvictionAttributes defaultEvictionAttributes = EvictionAttributes.createLRUEntryAttributes();
assertEvictionAttributes(applicationContext.getBean("PartitionRegion", Region.class), defaultEvictionAttributes);
assertEvictionAttributes(applicationContext.getBean("ReplicateRegion", Region.class), defaultEvictionAttributes);
}
@Test
public void usesCustomEvictionPolicyConfiguration() {
applicationContext = newApplicationContext(CustomEvictionPolicyConfiguration.class);
ObjectSizer mockObjectSizer = applicationContext.getBean("mockObjectSizer", ObjectSizer.class);
EvictionAttributes customEvictionAttributes = newEvictionAttributes(65536, EvictionPolicyType.MEMORY_SIZE,
EvictionActionType.OVERFLOW_TO_DISK, mockObjectSizer);
assertEvictionAttributes(applicationContext.getBean("PartitionRegion", Region.class), customEvictionAttributes);
assertEvictionAttributes(applicationContext.getBean("ReplicateRegion", Region.class), customEvictionAttributes);
}
@Test
public void usesRegionSpecificEvictionPolicyConfiguration() {
applicationContext = newApplicationContext(RegionSpecificEvictionPolicyConfiguration.class);
ObjectSizer mockObjectSizer = applicationContext.getBean("mockObjectSizer", ObjectSizer.class);
EvictionAttributes partitionRegionEvictionAttributes = newEvictionAttributes(null,
EvictionPolicyType.HEAP_PERCENTAGE, EvictionActionType.OVERFLOW_TO_DISK, mockObjectSizer);
EvictionAttributes replicateRegionEvictionAttributes = newEvictionAttributes(10000,
EvictionPolicyType.ENTRY_COUNT, EvictionActionType.LOCAL_DESTROY);
assertEvictionAttributes(applicationContext.getBean("PartitionRegion", Region.class),
partitionRegionEvictionAttributes);
assertEvictionAttributes(applicationContext.getBean("ReplicateRegion", Region.class),
replicateRegionEvictionAttributes);
}
@Test
public void usesLastMatchingEvictionPolicyConfiguration() {
applicationContext = newApplicationContext(LastMatchingWinsEvictionPolicyConfiguration.class);
EvictionAttributes lastMatchingEvictionAttributes = newEvictionAttributes(99, EvictionPolicyType.ENTRY_COUNT,
EvictionActionType.OVERFLOW_TO_DISK);
assertEvictionAttributes(applicationContext.getBean("PartitionRegion", Region.class),
lastMatchingEvictionAttributes);
assertEvictionAttributes(applicationContext.getBean("ReplicateRegion", Region.class),
lastMatchingEvictionAttributes);
}
@Configuration
@SuppressWarnings("unused")
static class CacheRegionConfiguration {
@Bean("mockCache")
@SuppressWarnings("unchecked")
Cache mockCache() {
Cache mockCache = mock(Cache.class);
RegionFactory mockRegionFactory = mock(RegionFactory.class);
final AtomicReference<EvictionAttributes> evictionAttributes =
new AtomicReference<EvictionAttributes>(null);
when(mockCache.createRegionFactory()).thenReturn(mockRegionFactory);
when(mockRegionFactory.setEvictionAttributes(any(EvictionAttributes.class))).thenAnswer(
new Answer<RegionFactory>() {
@Override
public RegionFactory answer(InvocationOnMock invocation) throws Throwable {
evictionAttributes.set(invocation.getArgumentAt(0, EvictionAttributes.class));
return (RegionFactory) invocation.getMock();
}
}
);
when(mockRegionFactory.create(anyString())).thenAnswer(new Answer<Region>() {
@Override
public Region answer(InvocationOnMock invocation) throws Throwable {
String regionName = invocation.getArgumentAt(0, String.class);
Region mockRegion = mock(Region.class, regionName);
RegionAttributes mockRegionAttributes =
mock(RegionAttributes.class, regionName.concat("Attributes"));
when(mockRegion.getName()).thenReturn(regionName);
when(mockRegion.getFullPath()).thenReturn(String.format("%1$s%2$s", Region.SEPARATOR, regionName));
when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes);
when(mockRegionAttributes.getEvictionAttributes()).thenReturn(evictionAttributes.get());
return mockRegion;
}
});
return mockCache;
}
@Bean("PartitionRegion")
PartitionedRegionFactoryBean<Object, Object> mockPartitionRegion(Cache gemfireCache) {
PartitionedRegionFactoryBean<Object, Object> partitionRegion =
new PartitionedRegionFactoryBean<Object, Object>();
partitionRegion.setCache(gemfireCache);
partitionRegion.setClose(false);
partitionRegion.setPersistent(false);
return partitionRegion;
}
@Bean("ReplicateRegion")
ReplicatedRegionFactoryBean<Object, Object> mockReplicateRegion(Cache gemfireCache) {
ReplicatedRegionFactoryBean<Object, Object> replicateRegion =
new ReplicatedRegionFactoryBean<Object, Object>();
replicateRegion.setCache(gemfireCache);
replicateRegion.setClose(false);
replicateRegion.setPersistent(false);
return replicateRegion;
}
@Bean
ObjectSizer mockObjectSizer() {
return mock(ObjectSizer.class);
}
}
@EnableEviction
static class DefaultEvictionPolicyConfiguration extends CacheRegionConfiguration {
}
@EnableEviction(policies = @EvictionPolicy(maximum = 65536, type = EvictionPolicyType.MEMORY_SIZE,
action = EvictionActionType.OVERFLOW_TO_DISK, objectSizerName = "mockObjectSizer"))
static class CustomEvictionPolicyConfiguration extends CacheRegionConfiguration {
}
@EnableEviction(policies = {
@EvictionPolicy(maximum = 85, type = EvictionPolicyType.HEAP_PERCENTAGE, action = EvictionActionType.OVERFLOW_TO_DISK,
objectSizerName = "mockObjectSizer", regionNames = "PartitionRegion"),
@EvictionPolicy(maximum = 10000, type = EvictionPolicyType.ENTRY_COUNT, action = EvictionActionType.LOCAL_DESTROY,
regionNames = "ReplicateRegion")
})
static class RegionSpecificEvictionPolicyConfiguration extends CacheRegionConfiguration {
}
@EnableEviction(policies = {
@EvictionPolicy(maximum = 1, type = EvictionPolicyType.ENTRY_COUNT, action = EvictionActionType.LOCAL_DESTROY,
objectSizerName = "mockObjectSizer", regionNames = "ReplicateRegion"),
@EvictionPolicy(maximum = 99, type = EvictionPolicyType.ENTRY_COUNT, action = EvictionActionType.OVERFLOW_TO_DISK)
})
static class LastMatchingWinsEvictionPolicyConfiguration extends CacheRegionConfiguration {
}
}