Implemented SGF-206 - CacheLoader and CacheWriter support on client, local Regions in a GemFire ClientCache. Also, performed additional refactoring of related code.
This commit is contained in:
@@ -31,6 +31,8 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.CacheClosedException;
|
||||
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.GemFireCache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
@@ -42,102 +44,92 @@ import com.gemstone.gemfire.cache.client.Pool;
|
||||
import com.gemstone.gemfire.internal.cache.GemFireCacheImpl;
|
||||
|
||||
/**
|
||||
* Client extension for GemFire regions.
|
||||
*
|
||||
* Client extension for GemFire Regions.
|
||||
* <p/>
|
||||
* @author Costin Leau
|
||||
* @author David Turanski
|
||||
* @author John Blum
|
||||
*/
|
||||
public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V> implements BeanFactoryAware,
|
||||
DisposableBean {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ClientRegionFactoryBean.class);
|
||||
|
||||
private boolean close = false;
|
||||
private boolean destroy = false;
|
||||
|
||||
private boolean close = true;
|
||||
|
||||
private Resource snapshot;
|
||||
|
||||
private CacheListener<K, V> cacheListeners[];
|
||||
|
||||
private Interest<K>[] interests;
|
||||
|
||||
private String poolName;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private CacheListener<K, V>[] cacheListeners;
|
||||
|
||||
private CacheLoader<K, V> cacheLoader;
|
||||
|
||||
private CacheWriter<K, V> cacheWriter;
|
||||
|
||||
private ClientRegionShortcut shortcut = null;
|
||||
|
||||
private DataPolicy dataPolicy;
|
||||
|
||||
private Interest<K>[] interests;
|
||||
|
||||
private RegionAttributes<K, V> attributes;
|
||||
|
||||
private Region<K, V> region;
|
||||
|
||||
private String diskStoreName;
|
||||
private Resource snapshot;
|
||||
|
||||
private String dataPolicyName;
|
||||
private String diskStoreName;
|
||||
private String poolName;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
super.afterPropertiesSet();
|
||||
region = getRegion();
|
||||
postProcess(region);
|
||||
postProcess(getRegion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region<K, V> lookupFallback(GemFireCache cache, String regionName) throws Exception {
|
||||
Assert.isTrue(cache instanceof ClientCache, "Unable to create regions from " + cache);
|
||||
ClientCache c = (ClientCache) cache;
|
||||
Assert.isTrue(cache instanceof ClientCache, String.format("Unable to create regions from %1$s", cache));
|
||||
|
||||
// TODO reference to an internal GemFire class!
|
||||
if (cache instanceof GemFireCacheImpl) {
|
||||
Assert.isTrue(((GemFireCacheImpl) cache).isClient(), "A client-cache instance is required");
|
||||
}
|
||||
|
||||
Assert.isTrue(!(StringUtils.hasText(dataPolicyName) && dataPolicy != null), "Only one of 'dataPolicy' or 'dataPolicyName' can be set");
|
||||
|
||||
|
||||
|
||||
Assert.isTrue(!(StringUtils.hasText(dataPolicyName) && dataPolicy != null),
|
||||
"Only one of 'dataPolicy' or 'dataPolicyName' can be set");
|
||||
|
||||
if (StringUtils.hasText(dataPolicyName)) {
|
||||
dataPolicy = new DataPolicyConverter().convert(dataPolicyName);
|
||||
Assert.notNull(dataPolicy, "Data policy " + dataPolicyName + " is invalid");
|
||||
}
|
||||
|
||||
// first look at shortcut
|
||||
ClientRegionShortcut s = null;
|
||||
ClientRegionShortcut shortcut = this.shortcut;
|
||||
|
||||
if (shortcut == null) {
|
||||
if (dataPolicy != null) {
|
||||
if (DataPolicy.EMPTY.equals(dataPolicy)) {
|
||||
s = ClientRegionShortcut.PROXY;
|
||||
}
|
||||
else if (DataPolicy.PERSISTENT_REPLICATE.equals(dataPolicy)) {
|
||||
s = ClientRegionShortcut.LOCAL_PERSISTENT;
|
||||
shortcut = ClientRegionShortcut.PROXY;
|
||||
}
|
||||
else if (DataPolicy.NORMAL.equals(this.dataPolicy)) {
|
||||
s = ClientRegionShortcut.CACHING_PROXY;
|
||||
shortcut = ClientRegionShortcut.CACHING_PROXY;
|
||||
}
|
||||
else if (DataPolicy.PERSISTENT_REPLICATE.equals(dataPolicy)) {
|
||||
shortcut = ClientRegionShortcut.LOCAL_PERSISTENT;
|
||||
}
|
||||
else {
|
||||
s = ClientRegionShortcut.LOCAL;
|
||||
shortcut = ClientRegionShortcut.LOCAL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
s = ClientRegionShortcut.LOCAL;
|
||||
shortcut = ClientRegionShortcut.LOCAL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
s = shortcut;
|
||||
}
|
||||
|
||||
ClientRegionFactory<K, V> factory = c.createClientRegionFactory(s);
|
||||
ClientRegionFactory<K, V> factory = ((ClientCache) cache).createClientRegionFactory(shortcut);
|
||||
|
||||
// map the attributes onto the client
|
||||
if (attributes != null) {
|
||||
CacheListener<K, V>[] listeners = attributes.getCacheListeners();
|
||||
if (!ObjectUtils.isEmpty(listeners)) {
|
||||
for (CacheListener<K, V> listener : listeners) {
|
||||
factory.addCacheListener(listener);
|
||||
}
|
||||
}
|
||||
factory.setCloningEnabled(attributes.getCloningEnabled());
|
||||
factory.setConcurrencyLevel(attributes.getConcurrencyLevel());
|
||||
factory.setCustomEntryIdleTimeout(attributes.getCustomEntryIdleTimeout());
|
||||
@@ -157,11 +149,7 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
|
||||
factory.setValueConstraint(attributes.getValueConstraint());
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(cacheListeners)) {
|
||||
for (CacheListener<K, V> listener : cacheListeners) {
|
||||
factory.addCacheListener(listener);
|
||||
}
|
||||
}
|
||||
addCacheListeners(factory);
|
||||
|
||||
if (StringUtils.hasText(poolName)) {
|
||||
// try to eagerly initialize the pool name, if defined as a bean
|
||||
@@ -173,7 +161,8 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
|
||||
}
|
||||
|
||||
factory.setPoolName(poolName);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Pool pool = beanFactory.getBean(Pool.class);
|
||||
factory.setPoolName(pool.getName());
|
||||
}
|
||||
@@ -182,35 +171,71 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
|
||||
factory.setDiskStoreName(diskStoreName);
|
||||
}
|
||||
|
||||
Region<K, V> reg = factory.create(regionName);
|
||||
Region<K, V> clientRegion = factory.create(regionName);
|
||||
log.info("Created new cache region [" + regionName + "]");
|
||||
|
||||
if (snapshot != null) {
|
||||
reg.loadSnapshot(snapshot.getInputStream());
|
||||
clientRegion.loadSnapshot(snapshot.getInputStream());
|
||||
}
|
||||
|
||||
return reg;
|
||||
return clientRegion;
|
||||
}
|
||||
|
||||
private void addCacheListeners(ClientRegionFactory<K, V> factory) {
|
||||
if (attributes != null) {
|
||||
CacheListener<K, V>[] listeners = attributes.getCacheListeners();
|
||||
|
||||
if (!ObjectUtils.isEmpty(listeners)) {
|
||||
for (CacheListener<K, V> listener : listeners) {
|
||||
factory.addCacheListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(cacheListeners)) {
|
||||
for (CacheListener<K, V> listener : cacheListeners) {
|
||||
factory.addCacheListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void postProcess(Region<K, V> region) {
|
||||
registerInterests(region);
|
||||
setCacheLoader(region);
|
||||
setCacheWriter(region);
|
||||
}
|
||||
|
||||
private void registerInterests(final Region<K, V> region) {
|
||||
if (!ObjectUtils.isEmpty(interests)) {
|
||||
for (Interest<K> interest : interests) {
|
||||
if (interest instanceof RegexInterest) {
|
||||
// do the cast since it's safe
|
||||
region.registerInterestRegex((String) interest.getKey(), interest.getPolicy(),
|
||||
interest.isDurable(), interest.isReceiveValues());
|
||||
interest.isDurable(), interest.isReceiveValues());
|
||||
}
|
||||
else {
|
||||
region.registerInterest(interest.getKey(), interest.getPolicy(), interest.isDurable(),
|
||||
interest.isReceiveValues());
|
||||
interest.isReceiveValues());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setCacheLoader(Region<K, V> region) {
|
||||
if (cacheLoader != null) {
|
||||
region.getAttributesMutator().setCacheLoader(this.cacheLoader);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCacheWriter(Region<K, V> region) {
|
||||
if (cacheWriter != null) {
|
||||
region.getAttributesMutator().setCacheWriter(this.cacheWriter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
Region<K, V> region = getObject();
|
||||
// unregister interests
|
||||
|
||||
try {
|
||||
if (region != null && !ObjectUtils.isEmpty(interests)) {
|
||||
for (Interest<K> interest : interests) {
|
||||
@@ -222,9 +247,9 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
|
||||
}
|
||||
}
|
||||
}
|
||||
// should not really happen since interests are validated at
|
||||
// start/registration
|
||||
}
|
||||
// NOTE AdminRegion, LocalDataSet, Proxy Region and RegionCreation all throw UnsupportedOperationException;
|
||||
// however, should not really happen since Interests are validated at start/registration
|
||||
catch (UnsupportedOperationException ex) {
|
||||
log.warn("Cannot unregister cache interests", ex);
|
||||
}
|
||||
@@ -235,16 +260,16 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
|
||||
try {
|
||||
region.close();
|
||||
}
|
||||
catch (CacheClosedException cce) {
|
||||
// nothing to see folks, move on.
|
||||
catch (CacheClosedException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO I think Region.close and Region.destroyRegion are mutually exclusive; thus, 1 operation (e.g. close)
|
||||
// does not cancel the other (i.e. destroy). This should just be if, not else if.
|
||||
else if (destroy) {
|
||||
region.destroyRegion();
|
||||
}
|
||||
}
|
||||
region = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,42 +319,44 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
|
||||
* recommended way for creating clients since it covers all the major
|
||||
* scenarios with minimal configuration.
|
||||
*
|
||||
* @param shortcut
|
||||
* @param shortcut the ClientRegionShortcut to use.
|
||||
*/
|
||||
public void setShortcut(ClientRegionShortcut shortcut) {
|
||||
this.shortcut = shortcut;
|
||||
}
|
||||
|
||||
final boolean isDestroy() {
|
||||
return destroy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the region referred by this factory bean, will be
|
||||
* 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.
|
||||
*
|
||||
* <p/>
|
||||
* @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;
|
||||
}
|
||||
|
||||
if (destroy) {
|
||||
close = false;
|
||||
}
|
||||
final boolean isClose() {
|
||||
return close;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the region referred by this factory bean, will be
|
||||
* closed on shutdown (default true). Note: destroy and close are mutually
|
||||
* exclusive. Enabling one will automatically disable the other.
|
||||
*
|
||||
* <p/>
|
||||
* @param close whether to close or not the region
|
||||
* @see #setDestroy(boolean)
|
||||
*/
|
||||
public void setClose(boolean close) {
|
||||
this.close = close;
|
||||
if (close) {
|
||||
destroy = false;
|
||||
}
|
||||
this.destroy = (this.destroy && !close); // retain previous value iff close is false.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,6 +382,26 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
|
||||
this.cacheListeners = cacheListeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CacheLoader used to load data local to the client's Region on cache misses.
|
||||
* <p/>
|
||||
* @param cacheLoader a GemFire CacheLoader used to load data into the client Region.
|
||||
* @see com.gemstone.gemfire.cache.CacheLoader
|
||||
*/
|
||||
public void setCacheLoader(CacheLoader<K, V> cacheLoader) {
|
||||
this.cacheLoader = cacheLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CacheWriter used to perform a synchronous write-behind when data is put into the client's Region.
|
||||
* <p/>
|
||||
* @param cacheWriter the GemFire CacheWriter used to perform synchronous write-behinds on put ops.
|
||||
* @see com.gemstone.gemfire.cache.CacheWriter
|
||||
*/
|
||||
public void setCacheWriter(CacheWriter<K, V> cacheWriter) {
|
||||
this.cacheWriter = cacheWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data policy. Used only when a new region is created.
|
||||
*
|
||||
@@ -395,4 +442,5 @@ public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V>
|
||||
public void setAttributes(RegionAttributes<K, V> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,10 +33,9 @@ import com.gemstone.gemfire.cache.DataPolicy;
|
||||
|
||||
/**
|
||||
* Parser for <client-region;gt; definitions.
|
||||
*
|
||||
* To avoid eager evaluations, the region interests are declared as nested
|
||||
* definition.
|
||||
*
|
||||
* <p/>
|
||||
* To avoid eager evaluations, the region interests are declared as nested definition.
|
||||
* <p/>
|
||||
* @author Costin Leau
|
||||
* @author David Turanski
|
||||
* @author John Blum
|
||||
@@ -52,87 +51,92 @@ class ClientRegionParser extends AbstractRegionParser {
|
||||
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
|
||||
super.doParse(element, builder);
|
||||
|
||||
// set scope
|
||||
// since the user can define both client and p2p regions
|
||||
// setting the cache/DS to a be 'loner' isn't feasible
|
||||
// so to prevent both client and p2p communication in the region,
|
||||
// the scope is fixed to local
|
||||
String cacheRefAttribute = element.getAttribute("cache-ref");
|
||||
|
||||
builder.addPropertyReference("cache", (StringUtils.hasText(cacheRefAttribute) ? cacheRefAttribute
|
||||
: GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME));
|
||||
|
||||
ParsingUtils.setPropertyValue(element, builder, "close");
|
||||
ParsingUtils.setPropertyValue(element, builder, "destroy");
|
||||
ParsingUtils.setPropertyValue(element, builder, "data-policy", "dataPolicyName");
|
||||
ParsingUtils.setPropertyValue(element, builder, "name");
|
||||
ParsingUtils.setPropertyValue(element, builder, "pool-name");
|
||||
ParsingUtils.setPropertyValue(element, builder, "shortcut");
|
||||
|
||||
parseDiskStoreAttribute(element, builder);
|
||||
|
||||
boolean dataPolicyFrozen = false;
|
||||
|
||||
// TODO why is the DataPolicy determined in the ClientRegionParser and not in the ClientRegionFactoryBean when evaluating the configuration settings?
|
||||
// set the persistent policy
|
||||
String attr = element.getAttribute("persistent");
|
||||
|
||||
boolean frozenDataPolicy = false;
|
||||
|
||||
if (Boolean.parseBoolean(attr)) {
|
||||
// check first for GemFire 6.5
|
||||
if (Boolean.parseBoolean(element.getAttribute("persistent"))) {
|
||||
builder.addPropertyValue("dataPolicy", DataPolicy.PERSISTENT_REPLICATE);
|
||||
frozenDataPolicy = true;
|
||||
dataPolicyFrozen = true;
|
||||
}
|
||||
|
||||
attr = element.getAttribute("cache-ref");
|
||||
// add cache reference (fallback to default if nothing is specified)
|
||||
builder.addPropertyReference("cache", (StringUtils.hasText(attr) ? attr : GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME));
|
||||
ParsingUtils.setPropertyValue(element, builder, "close");
|
||||
ParsingUtils.setPropertyValue(element, builder, "destroy");
|
||||
// eviction + overflow attributes
|
||||
// client attributes
|
||||
BeanDefinitionBuilder attrBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(RegionAttributesFactoryBean.class);
|
||||
// eviction + expiration + overflow + optional client region attributes
|
||||
BeanDefinitionBuilder regionAttributesBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
RegionAttributesFactoryBean.class);
|
||||
|
||||
ParsingUtils.parseStatistics(element, attrBuilder);
|
||||
boolean overwriteDataPolicy = ParsingUtils.parseEviction(parserContext, element, regionAttributesBuilder);
|
||||
|
||||
if (StringUtils.hasText(element.getAttribute("disk-store-ref"))) {
|
||||
ParsingUtils.setPropertyValue(element, builder, "disk-store-ref", "diskStoreName");
|
||||
builder.addDependsOn(element.getAttribute("disk-store-ref"));
|
||||
}
|
||||
ParsingUtils.parseOptionalRegionAttributes(parserContext, element, regionAttributesBuilder);
|
||||
ParsingUtils.parseStatistics(element, regionAttributesBuilder);
|
||||
// NOTE parsing 'expiration' attributes must happen after parsing the user-defined setting for 'statistics'
|
||||
// in GemFire this attribute corresponds to the RegionAttributes 'statistics-enabled' setting). If the user
|
||||
// configured expiration settings (any?), then statistics must be enabled, regardless if the user explicitly
|
||||
// disabled them.
|
||||
ParsingUtils.parseExpiration(parserContext, element, regionAttributesBuilder);
|
||||
|
||||
boolean overwriteDataPolicy = false;
|
||||
|
||||
overwriteDataPolicy |= ParsingUtils.parseEviction(parserContext, element, attrBuilder);
|
||||
ParsingUtils.parseStatistics(element, attrBuilder);
|
||||
ParsingUtils.parseExpiration(parserContext, element, attrBuilder);
|
||||
ParsingUtils.parseEviction(parserContext, element, attrBuilder);
|
||||
ParsingUtils.parseOptionalRegionAttributes(parserContext, element, attrBuilder);
|
||||
|
||||
if (!frozenDataPolicy && overwriteDataPolicy) {
|
||||
// TODO understand why this determination is not made in the FactoryBean?
|
||||
if (!dataPolicyFrozen && overwriteDataPolicy) {
|
||||
builder.addPropertyValue("dataPolicy", DataPolicy.NORMAL);
|
||||
}
|
||||
|
||||
builder.addPropertyValue("attributes", attrBuilder.getBeanDefinition());
|
||||
|
||||
|
||||
builder.addPropertyValue("attributes", regionAttributesBuilder.getBeanDefinition());
|
||||
|
||||
List<Element> subElements = DomUtils.getChildElements(element);
|
||||
ManagedList<Object> interests = new ManagedList<Object>();
|
||||
|
||||
// parse nested declarations
|
||||
List<Element> subElements = DomUtils.getChildElements(element);
|
||||
|
||||
// parse nested cache-listener elements
|
||||
// parse nested elements
|
||||
for (Element subElement : subElements) {
|
||||
String name = subElement.getLocalName();
|
||||
String subElementLocalName = subElement.getLocalName();
|
||||
|
||||
if ("cache-listener".equals(name)) {
|
||||
builder.addPropertyValue("cacheListeners", parseCacheListener(parserContext, subElement, builder));
|
||||
if ("cache-listener".equals(subElementLocalName)) {
|
||||
builder.addPropertyValue("cacheListeners", ParsingUtils.parseRefOrNestedBeanDeclaration(
|
||||
parserContext, subElement, builder));
|
||||
}
|
||||
|
||||
else if ("key-interest".equals(name)) {
|
||||
else if ("cache-loader".equals(subElementLocalName)) {
|
||||
builder.addPropertyValue("cacheLoader", ParsingUtils.parseRefOrNestedBeanDeclaration(
|
||||
parserContext, subElement, builder));
|
||||
}
|
||||
else if ("cache-writer".equals(subElementLocalName)) {
|
||||
builder.addPropertyValue("cacheWriter", ParsingUtils.parseRefOrNestedBeanDeclaration(
|
||||
parserContext, subElement, builder));
|
||||
}
|
||||
else if ("key-interest".equals(subElementLocalName)) {
|
||||
interests.add(parseKeyInterest(parserContext, subElement));
|
||||
}
|
||||
|
||||
else if ("regex-interest".equals(name)) {
|
||||
else if ("regex-interest".equals(subElementLocalName)) {
|
||||
interests.add(parseRegexInterest(parserContext, subElement));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO is adding an 'interests' property really based on whether there are "sub-elements", or should it be based on whether there are "interests" (as in 'key-interest' and 'regex-interest')?
|
||||
if (!subElements.isEmpty()) {
|
||||
builder.addPropertyValue("interests", interests);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseDiskStoreAttribute(Element element, BeanDefinitionBuilder builder) {
|
||||
String diskStoreRefAttribute = element.getAttribute("disk-store-ref");
|
||||
|
||||
if (StringUtils.hasText(diskStoreRefAttribute)) {
|
||||
builder.addPropertyValue("diskStoreName", diskStoreRefAttribute);
|
||||
builder.addDependsOn(diskStoreRefAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doParseRegion(Element element, ParserContext parserContext, BeanDefinitionBuilder builder,
|
||||
boolean subRegion) {
|
||||
@@ -141,32 +145,32 @@ class ClientRegionParser extends AbstractRegionParser {
|
||||
getClass().getName()));
|
||||
}
|
||||
|
||||
private Object parseCacheListener(ParserContext parserContext, Element subElement, BeanDefinitionBuilder builder) {
|
||||
return ParsingUtils.parseRefOrNestedBeanDeclaration(parserContext, subElement, builder);
|
||||
}
|
||||
|
||||
private Object parseKeyInterest(ParserContext parserContext, Element subElement) {
|
||||
BeanDefinitionBuilder keyInterestBuilder = BeanDefinitionBuilder.genericBeanDefinition(Interest.class);
|
||||
parseCommonInterestAttr(subElement, keyInterestBuilder);
|
||||
|
||||
parseCommonInterestAttributes(subElement, keyInterestBuilder);
|
||||
|
||||
Object key = ParsingUtils.parseRefOrNestedBeanDeclaration(parserContext, subElement, keyInterestBuilder,
|
||||
"key-ref");
|
||||
"key-ref");
|
||||
|
||||
keyInterestBuilder.addConstructorArgValue(key);
|
||||
|
||||
return keyInterestBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
private Object parseRegexInterest(ParserContext parserContext, Element subElement) {
|
||||
BeanDefinitionBuilder regexInterestBuilder = BeanDefinitionBuilder.genericBeanDefinition(RegexInterest.class);
|
||||
|
||||
parseCommonInterestAttr(subElement, regexInterestBuilder);
|
||||
parseCommonInterestAttributes(subElement, regexInterestBuilder);
|
||||
ParsingUtils.setPropertyValue(subElement, regexInterestBuilder, "pattern", "key");
|
||||
|
||||
return regexInterestBuilder.getBeanDefinition();
|
||||
}
|
||||
|
||||
private void parseCommonInterestAttr(Element element, BeanDefinitionBuilder builder) {
|
||||
private void parseCommonInterestAttributes(Element element, BeanDefinitionBuilder builder) {
|
||||
ParsingUtils.setPropertyValue(element, builder, "durable", "durable");
|
||||
ParsingUtils.setPropertyValue(element, builder, "result-policy", "policy");
|
||||
ParsingUtils.setPropertyValue(element, builder, "receive-values", "receiveValues");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,15 +20,10 @@ import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeanMetadataAttribute;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.ManagedArray;
|
||||
import org.springframework.beans.factory.support.ManagedList;
|
||||
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.core.Conventions;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -44,21 +39,18 @@ import com.gemstone.gemfire.cache.ResumptionAction;
|
||||
import com.gemstone.gemfire.cache.Scope;
|
||||
|
||||
/**
|
||||
* Various minor utility used by the parser.
|
||||
*
|
||||
* Various minor utilities used by the parser.
|
||||
* <p/>
|
||||
* @author Costin Leau
|
||||
* @author David Turanski
|
||||
* @author Lyndon Adams
|
||||
* @author John Blum
|
||||
*/
|
||||
abstract class ParsingUtils {
|
||||
|
||||
|
||||
private static Log log = LogFactory.getLog(ParsingUtils.class);
|
||||
|
||||
final static String GEMFIRE_VERSION = CacheFactory.getVersion();
|
||||
|
||||
private static final String ALIASES_KEY = ParsingUtils.class.getName() + ":aliases";
|
||||
|
||||
|
||||
static final String GEMFIRE_VERSION = CacheFactory.getVersion();
|
||||
|
||||
static void setPropertyValue(Element element, BeanDefinitionBuilder builder, String attributeName,
|
||||
String propertyName, Object defaultValue) {
|
||||
@@ -90,35 +82,6 @@ abstract class ParsingUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for parsing bean aliases. Normally parsed by
|
||||
* AbstractBeanDefinitionParser however due to the attribute clash (bean
|
||||
* uses 'name' for aliases while region use it to indicate their name), the
|
||||
* parser needs to handle this differently by storing them as metadata which
|
||||
* gets deleted just before registration.
|
||||
*
|
||||
* @param element
|
||||
* @param builder
|
||||
*/
|
||||
static void addBeanAliasAsMetadata(Element element, BeanDefinitionBuilder builder) {
|
||||
String[] aliases = new String[0];
|
||||
String name = element.getAttributeNS(BeanDefinitionParserDelegate.BEANS_NAMESPACE_URI,
|
||||
AbstractBeanDefinitionParser.NAME_ATTRIBUTE);
|
||||
|
||||
if (StringUtils.hasLength(name)) {
|
||||
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
|
||||
}
|
||||
BeanMetadataAttribute attr = new BeanMetadataAttribute(ALIASES_KEY, aliases);
|
||||
attr.setSource(element);
|
||||
builder.getRawBeanDefinition().addMetadataAttribute(attr);
|
||||
}
|
||||
|
||||
static BeanDefinitionHolder replaceBeanAliasAsMetadata(BeanDefinitionHolder holder) {
|
||||
BeanDefinition beanDefinition = holder.getBeanDefinition();
|
||||
return new BeanDefinitionHolder(beanDefinition, holder.getBeanName(),
|
||||
(String[]) beanDefinition.removeAttribute(ALIASES_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method handling parsing of nested definition of the type:
|
||||
*
|
||||
@@ -134,32 +97,28 @@ abstract class ParsingUtils {
|
||||
* </tag>
|
||||
* </pre>
|
||||
*
|
||||
* @param element
|
||||
* @return
|
||||
* @param element the XML element.
|
||||
* @return Bean reference or nested Bean definition.
|
||||
*/
|
||||
static Object parseRefOrNestedBeanDeclaration(ParserContext parserContext, Element element,
|
||||
BeanDefinitionBuilder builder) {
|
||||
return parseRefOrNestedBeanDeclaration(parserContext, element, builder, "ref", false);
|
||||
}
|
||||
|
||||
static Object getBeanReference(ParserContext parserContext, Element element) {
|
||||
return getBeanReference(parserContext, element, "ref");
|
||||
}
|
||||
static Object getBeanReference(ParserContext parserContext, Element element, String refAttributeName) {
|
||||
String refAttributeValue = element.getAttribute(refAttributeName);
|
||||
|
||||
static Object getBeanReference(ParserContext parserContext, Element element, String refAttrName) {
|
||||
String attr = element.getAttribute(refAttrName);
|
||||
boolean hasRef = StringUtils.hasText(attr);
|
||||
|
||||
// check nested declarations
|
||||
// check nested bean declarations
|
||||
List<Element> childElements = DomUtils.getChildElements(element);
|
||||
|
||||
if (hasRef) {
|
||||
if (StringUtils.hasText(refAttributeValue)) {
|
||||
if (!childElements.isEmpty()) {
|
||||
parserContext.getReaderContext().error(
|
||||
"either use the '" + refAttrName + "' attribute or a nested bean declaration for '"
|
||||
+ element.getLocalName() + "' element, but not both", element);
|
||||
parserContext.getReaderContext().error(String.format(
|
||||
"Use either the '%1$s' attribute or a nested bean declaration for '%2$s' element, but not both",
|
||||
refAttributeName, element.getLocalName()), element);
|
||||
}
|
||||
return new RuntimeBeanReference(attr);
|
||||
|
||||
return new RuntimeBeanReference(refAttributeValue);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@@ -183,91 +142,93 @@ abstract class ParsingUtils {
|
||||
}
|
||||
|
||||
static Object parseRefOrNestedBeanDeclaration(ParserContext parserContext, Element element,
|
||||
BeanDefinitionBuilder builder, String refAttrName) {
|
||||
return parseRefOrNestedBeanDeclaration(parserContext, element, builder, refAttrName, false);
|
||||
BeanDefinitionBuilder builder, String refAttributeName) {
|
||||
return parseRefOrNestedBeanDeclaration(parserContext, element, builder, refAttributeName, false);
|
||||
}
|
||||
|
||||
static Object parseRefOrNestedBeanDeclaration(ParserContext parserContext, Element element,
|
||||
BeanDefinitionBuilder builder, String refAttrName, boolean single) {
|
||||
Object beanRef = getBeanReference(parserContext, element, refAttrName);
|
||||
if (beanRef != null) {
|
||||
return beanRef;
|
||||
BeanDefinitionBuilder builder, String refAttributeName, boolean single) {
|
||||
Object beanReference = getBeanReference(parserContext, element, refAttributeName);
|
||||
|
||||
if (beanReference != null) {
|
||||
return beanReference;
|
||||
}
|
||||
|
||||
// check nested declarations
|
||||
List<Element> childElements = DomUtils.getChildElements(element);
|
||||
|
||||
// nested parse nested bean definition
|
||||
// parse nested bean definition
|
||||
if (childElements.size() == 1) {
|
||||
return parserContext.getDelegate().parsePropertySubElement(childElements.get(0),
|
||||
builder.getRawBeanDefinition());
|
||||
}
|
||||
else {
|
||||
// TODO also triggered when there are no child elements; need to change the message...
|
||||
if (single) {
|
||||
parserContext.getReaderContext().error(
|
||||
"the element '" + element.getLocalName()
|
||||
+ "' does not support multiple nested bean definitions", element);
|
||||
parserContext.getReaderContext().error(String.format(
|
||||
"The element '%1$s' does not support multiple nested bean definitions",
|
||||
element.getLocalName()), element);
|
||||
}
|
||||
}
|
||||
|
||||
ManagedList<Object> list = new ManagedList<Object>();
|
||||
|
||||
for (Element el : childElements) {
|
||||
list.add(parserContext.getDelegate().parsePropertySubElement(el, builder.getRawBeanDefinition()));
|
||||
for (Element childElement : childElements) {
|
||||
list.add(parserContext.getDelegate().parsePropertySubElement(childElement, builder.getRawBeanDefinition()));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the eviction sub-element. Populates the given attribute factory
|
||||
* with the proper attributes.
|
||||
*
|
||||
* @param parserContext
|
||||
* @param element
|
||||
* @param attrBuilder
|
||||
* @return true if parsing actually occured, false otherwise
|
||||
* Parses the eviction sub-element. Populates the given attribute factory with the proper attributes.
|
||||
* <p/>
|
||||
* @param parserContext the context used for parsing the XML document.
|
||||
* @param element the XML elements being parsed.
|
||||
* @param attributesBuilder the Region Attributes builder.
|
||||
* @return true if parsing actually occurred, false otherwise.
|
||||
*/
|
||||
static boolean parseEviction(ParserContext parserContext, Element element, BeanDefinitionBuilder attrBuilder) {
|
||||
static boolean parseEviction(ParserContext parserContext, Element element, BeanDefinitionBuilder attributesBuilder) {
|
||||
Element evictionElement = DomUtils.getChildElementByTagName(element, "eviction");
|
||||
|
||||
if (evictionElement == null)
|
||||
return false;
|
||||
if (evictionElement != null) {
|
||||
BeanDefinitionBuilder evictionAttributesBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
EvictionAttributesFactoryBean.class);
|
||||
|
||||
BeanDefinitionBuilder evictionDefBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(EvictionAttributesFactoryBean.class);
|
||||
setPropertyValue(evictionElement, evictionAttributesBuilder, "action");
|
||||
setPropertyValue(evictionElement, evictionAttributesBuilder, "threshold");
|
||||
|
||||
// do manual conversion since the enum is not public
|
||||
String attr = evictionElement.getAttribute("type");
|
||||
if (StringUtils.hasText(attr)) {
|
||||
evictionDefBuilder.addPropertyValue("type", EvictionType.valueOf(attr.toUpperCase()));
|
||||
String evictionType = evictionElement.getAttribute("type");
|
||||
|
||||
if (StringUtils.hasText(evictionType)) {
|
||||
evictionAttributesBuilder.addPropertyValue("type", EvictionType.valueOf(evictionType.toUpperCase()));
|
||||
}
|
||||
|
||||
Element objectSizerElement = DomUtils.getChildElementByTagName(evictionElement, "object-sizer");
|
||||
|
||||
if (objectSizerElement != null) {
|
||||
Object sizer = parseRefOrNestedBeanDeclaration(parserContext, objectSizerElement,
|
||||
evictionAttributesBuilder);
|
||||
evictionAttributesBuilder.addPropertyValue("ObjectSizer", sizer);
|
||||
}
|
||||
|
||||
attributesBuilder.addPropertyValue("evictionAttributes", evictionAttributesBuilder.getBeanDefinition());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
setPropertyValue(evictionElement, evictionDefBuilder, "threshold");
|
||||
setPropertyValue(evictionElement, evictionDefBuilder, "action");
|
||||
|
||||
// get object sizer (if declared)
|
||||
Element objectSizerElement = DomUtils.getChildElementByTagName(evictionElement, "object-sizer");
|
||||
|
||||
if (objectSizerElement != null) {
|
||||
Object sizer = parseRefOrNestedBeanDeclaration(parserContext, objectSizerElement, evictionDefBuilder);
|
||||
evictionDefBuilder.addPropertyValue("ObjectSizer", sizer);
|
||||
}
|
||||
|
||||
attrBuilder.addPropertyValue("evictionAttributes", evictionDefBuilder.getBeanDefinition());
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the subscription sub-element. Populates the given attribute factory
|
||||
* with the proper attributes.
|
||||
*
|
||||
* @author Lyndon Adams
|
||||
* @param parserContext
|
||||
* @param element
|
||||
* @param attrBuilder
|
||||
* @return true if parsing actually occured, false otherwise
|
||||
* Parses the subscription sub-element. Populates the given attribute factory with the proper attributes.
|
||||
* <p/>
|
||||
* @param parserContext the context used while parsing the XML document.
|
||||
* @param element the XML element being parsed.
|
||||
* @param attrBuilder the Region Attributes builder.
|
||||
* @return true if parsing actually occurred, false otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
static boolean parseSubscription(ParserContext parserContext, Element element, BeanDefinitionBuilder attrBuilder) {
|
||||
Element subscriptionElement = DomUtils.getChildElementByTagName(element, "subscription");
|
||||
|
||||
@@ -301,23 +262,23 @@ abstract class ParsingUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the expiration sub-elements. Populates the given attribute factory
|
||||
* with proper attributes.
|
||||
*
|
||||
* @param parserContext
|
||||
* @param element
|
||||
* @param attrBuilder
|
||||
* @return
|
||||
* Parses the expiration sub-elements. Populates the given attribute factory with proper attributes.
|
||||
* <p/>
|
||||
* @param parserContext the context used while parsing the XML document.
|
||||
* @param element the XML element being parsed.
|
||||
* @param attrBuilder the Region Attributes builder.
|
||||
* @return a boolean indicating whether Region expiration attributes were specified.
|
||||
*/
|
||||
static boolean parseExpiration(ParserContext parserContext, Element element, BeanDefinitionBuilder attrBuilder) {
|
||||
boolean result = false;
|
||||
result |= parseExpiration(element, "region-ttl", "regionTimeToLive", attrBuilder);
|
||||
boolean result = parseExpiration(element, "region-ttl", "regionTimeToLive", attrBuilder);
|
||||
|
||||
result |= parseExpiration(element, "region-tti", "regionIdleTimeout", attrBuilder);
|
||||
result |= parseExpiration(element, "entry-ttl", "entryTimeToLive", attrBuilder);
|
||||
result |= parseExpiration(element, "entry-tti", "entryIdleTimeout", attrBuilder);
|
||||
result |= parseCustomExpiration(parserContext, element,"custom-entry-ttl","customEntryTimeToLive",attrBuilder);
|
||||
result |= parseCustomExpiration(parserContext, element,"custom-entry-tti","customEntryIdleTimeout",attrBuilder);
|
||||
|
||||
// TODO why?
|
||||
if (result) {
|
||||
// turn on statistics
|
||||
attrBuilder.addPropertyValue("statisticsEnabled", Boolean.TRUE);
|
||||
@@ -469,4 +430,4 @@ abstract class ParsingUtils {
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,8 +443,7 @@ The name of the region definition.]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:complexType>
|
||||
<!-- -->
|
||||
<xsd:complexType name="baseReadOnlyRegionType"
|
||||
abstract="true">
|
||||
<xsd:complexType name="baseReadOnlyRegionType" abstract="true">
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="basicRegionType">
|
||||
<xsd:sequence>
|
||||
@@ -502,7 +501,6 @@ Time to idle (or idle timeout) configuration for the region itself. Default: no
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:choice>
|
||||
<xsd:element name="entry-ttl" type="expirationType"
|
||||
minOccurs="0" maxOccurs="1">
|
||||
@@ -512,7 +510,6 @@ Time to live configuration for the region entries. Default: no expiration.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="custom-entry-ttl" type="customExpirationType"
|
||||
minOccurs="0" maxOccurs="1">
|
||||
<xsd:annotation>
|
||||
@@ -527,7 +524,6 @@ CustomExpiry time to live configuration for the region entries. Default: no expi
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
|
||||
<xsd:choice>
|
||||
<xsd:element name="entry-tti" type="expirationType"
|
||||
minOccurs="0" maxOccurs="1">
|
||||
@@ -678,7 +674,6 @@ The fully qualified class name of the expected value type
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
@@ -806,7 +801,7 @@ use inner bean declarations.
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="index-update-type" use="optional"
|
||||
<xsd:attribute name="index-update-type" use="optional"
|
||||
default="synchronous">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
@@ -1673,6 +1668,36 @@ which it receives its data. The client can hold some data locally or forward all
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="readOnlyRegionType">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cache-loader" type="beanDeclarationType"
|
||||
minOccurs="0" maxOccurs="1">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="com.gemstone.gemfire.cache.CacheLoader"><![CDATA[
|
||||
The cache loader definition for this region. A cache loader allows data to be placed into a region.
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation>
|
||||
<tool:exports type="com.gemstone.gemfire.cache.CacheLoader" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="cache-writer" type="beanDeclarationType"
|
||||
minOccurs="0" maxOccurs="1">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="com.gemstone.gemfire.cache.CacheWriter"><![CDATA[
|
||||
The cache writer definition for this region. A cache writer acts as a dedicated synchronous listener that is notified
|
||||
before a region or an entry is modified. A typical example would be a writer that updates the database.
|
||||
|
||||
Note: Only one CacheWriter is invoked. GemFire will always prefer the local one (if it exists) otherwise it will
|
||||
arbitrarily pick one.
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation>
|
||||
<tool:exports type="com.gemstone.gemfire.cache.CacheWriter" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="key-interest">
|
||||
<xsd:annotation>
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.gemfire.client;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
@@ -27,14 +30,18 @@ import com.gemstone.gemfire.cache.client.ClientRegionFactory;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
import com.gemstone.gemfire.cache.client.Pool;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
* @author John Blum
|
||||
*/
|
||||
public class ClientRegionFactoryBeanTest {
|
||||
|
||||
@Test
|
||||
public void testLookupFallbackFailingToUseProvidedShortcut()
|
||||
throws Exception {
|
||||
public void testLookupFallbackFailingToUseProvidedShortcut() throws Exception {
|
||||
ClientRegionFactoryBean<Object, Object> fb = new ClientRegionFactoryBean<Object, Object>();
|
||||
|
||||
fb.setShortcut(ClientRegionShortcut.CACHING_PROXY);
|
||||
|
||||
|
||||
Pool pool = Mockito.mock(Pool.class);
|
||||
|
||||
BeanFactory beanFactory = Mockito.mock(BeanFactory.class);
|
||||
@@ -47,11 +54,8 @@ public class ClientRegionFactoryBeanTest {
|
||||
ClientCache cache = Mockito.mock(ClientCache.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ClientRegionFactory<Object, Object> factory = Mockito
|
||||
.mock(ClientRegionFactory.class);
|
||||
Mockito.when(
|
||||
cache.createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY))
|
||||
.thenReturn(factory);
|
||||
ClientRegionFactory<Object, Object> factory = Mockito.mock(ClientRegionFactory.class);
|
||||
Mockito.when(cache.createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY)).thenReturn(factory);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Region<Object, Object> region = Mockito.mock(Region.class);
|
||||
@@ -61,4 +65,54 @@ public class ClientRegionFactoryBeanTest {
|
||||
|
||||
assertSame(region, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseDestroySettings() {
|
||||
final ClientRegionFactoryBean<Object, Object> factory = new ClientRegionFactoryBean<Object, Object>();
|
||||
|
||||
assertNotNull(factory);
|
||||
assertFalse(factory.isClose());
|
||||
assertFalse(factory.isDestroy());
|
||||
|
||||
factory.setClose(false);
|
||||
|
||||
assertFalse(factory.isClose());
|
||||
assertFalse(factory.isDestroy()); // when destroy is false it remains false even when setClose(false) is called
|
||||
|
||||
factory.setClose(true);
|
||||
|
||||
assertTrue(factory.isClose()); // calling setClose(true) should set close to true
|
||||
assertFalse(factory.isDestroy());
|
||||
|
||||
factory.setDestroy(false);
|
||||
|
||||
assertTrue(factory.isClose()); // calling setDestroy(false) should have no affect on close
|
||||
assertFalse(factory.isDestroy());
|
||||
|
||||
factory.setDestroy(true);
|
||||
|
||||
assertFalse(factory.isClose()); // setting destroy to true should set close to false
|
||||
assertTrue(factory.isDestroy()); // calling setDestroy(true) should set destroy to true
|
||||
|
||||
factory.setClose(false);
|
||||
|
||||
assertFalse(factory.isClose());
|
||||
assertTrue(factory.isDestroy()); // calling setClose(false) should have no affect on destroy
|
||||
|
||||
factory.setDestroy(false);
|
||||
|
||||
assertFalse(factory.isClose()); // setting destroy back to false should have no affect on close
|
||||
assertFalse(factory.isDestroy());
|
||||
|
||||
factory.setDestroy(true);
|
||||
|
||||
assertFalse(factory.isClose());
|
||||
assertTrue(factory.isDestroy());
|
||||
|
||||
factory.setClose(true);
|
||||
|
||||
assertTrue(factory.isClose());
|
||||
assertFalse(factory.isDestroy()); // setting close to true should set destroy to false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2010-2013 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.client;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.gemfire.ForkUtil;
|
||||
import org.springframework.data.gemfire.fork.SpringCacheServerProcess;
|
||||
import org.springframework.data.gemfire.test.GemfireTestApplicationContextInitializer;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.gemstone.gemfire.cache.CacheLoader;
|
||||
import com.gemstone.gemfire.cache.CacheLoaderException;
|
||||
import com.gemstone.gemfire.cache.CacheWriterException;
|
||||
import com.gemstone.gemfire.cache.EntryEvent;
|
||||
import com.gemstone.gemfire.cache.LoaderHelper;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.util.CacheWriterAdapter;
|
||||
|
||||
/**
|
||||
* The ClientRegionWithCacheLoaderWriterTest class is a test suite of test cases testing the addition of CacheLoaders
|
||||
* and CacheWriters to a client, local Region inside a GemFire Cache.
|
||||
* <p/>
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.junit.runner.RunWith
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
* @see com.gemstone.gemfire.cache.CacheLoader
|
||||
* @see com.gemstone.gemfire.cache.CacheWriter
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @since 1.3.3
|
||||
*/
|
||||
@ContextConfiguration(locations = "/org/springframework/data/gemfire/client/clientcache-with-region-using-cache-loader-writer.xml",
|
||||
initializers = GemfireTestApplicationContextInitializer.class)
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SuppressWarnings("unused")
|
||||
public class ClientRegionWithCacheLoaderWriterTest {
|
||||
|
||||
private static final int REGION_SIZE = 100;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
@Resource(name = "localAppDataRegion")
|
||||
private Region<Integer, Integer> localAppData;
|
||||
|
||||
@BeforeClass
|
||||
public static void startCacheServer() throws Exception {
|
||||
ForkUtil.startCacheServer(SpringCacheServerProcess.class.getName() + " "
|
||||
+ "/org/springframework/data/gemfire/client/servercache-with-region-for-client.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheLoaderWriter() {
|
||||
assertNotNull(localAppData);
|
||||
assertEquals(0, localAppData.size());
|
||||
|
||||
for (int key = 0; key < REGION_SIZE; key++) {
|
||||
assertEquals(key + 1, localAppData.get(key).intValue());
|
||||
}
|
||||
|
||||
assertEquals(REGION_SIZE, localAppData.size());
|
||||
|
||||
for (int key = 0; key < REGION_SIZE; key++) {
|
||||
assertEquals(key + 1, localAppData.put(key, REGION_SIZE - key).intValue());
|
||||
}
|
||||
|
||||
LocalAppDataCacheWriter localCacheWriter = context.getBean("localCacheWriter", LocalAppDataCacheWriter.class);
|
||||
|
||||
assertNotNull(localCacheWriter);
|
||||
|
||||
for (int key = 0; key < REGION_SIZE; key++) {
|
||||
assertEquals(REGION_SIZE - key, localCacheWriter.get(key).intValue());
|
||||
}
|
||||
}
|
||||
|
||||
public static class LocalAppDataCacheLoader implements CacheLoader<Integer, Integer> {
|
||||
|
||||
private static final AtomicInteger VALUE_GENERATOR = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public Integer load(final LoaderHelper<Integer, Integer> helper) throws CacheLoaderException {
|
||||
return VALUE_GENERATOR.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
|
||||
public static class LocalAppDataCacheWriter extends CacheWriterAdapter<Integer, Integer> {
|
||||
|
||||
private static final Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>(REGION_SIZE);
|
||||
|
||||
@Override
|
||||
public void beforeUpdate(final EntryEvent<Integer, Integer> event) throws CacheWriterException {
|
||||
data.put(event.getKey(), event.getNewValue());
|
||||
}
|
||||
|
||||
public Integer get(final Integer key) {
|
||||
return data.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,6 @@
|
||||
*/
|
||||
package org.springframework.data.gemfire.client;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
@@ -26,36 +25,41 @@ import org.springframework.data.gemfire.fork.SpringCacheServerProcess;
|
||||
|
||||
import com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.client.Pool;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
*
|
||||
*/
|
||||
|
||||
public class MultipleClientCacheTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void startUp() throws Exception {
|
||||
ForkUtil.startCacheServer(SpringCacheServerProcess.class.getName() + " "
|
||||
+ "/org/springframework/data/gemfire/client/datasource-server.xml");
|
||||
+ "/org/springframework/data/gemfire/client/datasource-server.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleCaches() {
|
||||
String resourcePath = "/org/springframework/data/gemfire/client/client-cache-no-close.xml";
|
||||
|
||||
ConfigurableApplicationContext ctx1 = new ClassPathXmlApplicationContext(resourcePath);
|
||||
ConfigurableApplicationContext ctx2 = new ClassPathXmlApplicationContext(resourcePath);
|
||||
Region region1 = ctx1.getBean(Region.class);
|
||||
Cache cache1 = ctx1.getBean(Cache.class);
|
||||
Pool pool = ctx1.getBean(Pool.class);
|
||||
Region region2 = ctx2.getBean(Region.class);
|
||||
Cache cache2 = ctx2.getBean(Cache.class);
|
||||
assertSame(region1,region2);
|
||||
assertSame(cache1,cache2);
|
||||
assertFalse(region1.isDestroyed());
|
||||
ctx1.close();
|
||||
|
||||
ConfigurableApplicationContext context1 = new ClassPathXmlApplicationContext(resourcePath);
|
||||
ConfigurableApplicationContext context2 = new ClassPathXmlApplicationContext(resourcePath);
|
||||
|
||||
Cache cache1 = context1.getBean(Cache.class);
|
||||
Cache cache2 = context2.getBean(Cache.class);
|
||||
|
||||
assertSame(cache1, cache2);
|
||||
|
||||
Region region1 = context1.getBean(Region.class);
|
||||
Region region2 = context2.getBean(Region.class);
|
||||
|
||||
assertSame(region1, region2);
|
||||
assertFalse(cache1.isClosed());
|
||||
assertFalse("region was destroyed" ,region1.isDestroyed());
|
||||
assertFalse(region1.isDestroyed());
|
||||
|
||||
context1.close();
|
||||
|
||||
assertFalse(cache1.isClosed());
|
||||
assertFalse("region was destroyed", region1.isDestroyed());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@@ -63,4 +67,5 @@ public class MultipleClientCacheTest {
|
||||
Thread.sleep(3000);
|
||||
ForkUtil.sendSignal();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.gemfire.config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -38,21 +39,27 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.CacheListener;
|
||||
import com.gemstone.gemfire.cache.CacheLoader;
|
||||
import com.gemstone.gemfire.cache.CacheLoaderException;
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.EvictionAction;
|
||||
import com.gemstone.gemfire.cache.EvictionAlgorithm;
|
||||
import com.gemstone.gemfire.cache.EvictionAttributes;
|
||||
import com.gemstone.gemfire.cache.LoaderHelper;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionAttributes;
|
||||
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
|
||||
import com.gemstone.gemfire.cache.util.CacheWriterAdapter;
|
||||
import com.gemstone.gemfire.cache.util.ObjectSizer;
|
||||
|
||||
/**
|
||||
* @author Costin Leau
|
||||
* @author David Turanski
|
||||
* @author John Blum
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(locations="client-ns.xml",
|
||||
@ContextConfiguration(locations="/org/springframework/data/gemfire/config/client-ns.xml",
|
||||
initializers=GemfireTestApplicationContextInitializer.class)
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class ClientRegionNamespaceTest {
|
||||
|
||||
@Autowired
|
||||
@@ -60,9 +67,7 @@ public class ClientRegionNamespaceTest {
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
|
||||
for (String name : new File(".").list(new FilenameFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.startsWith("BACKUP");
|
||||
@@ -94,8 +99,8 @@ public class ClientRegionNamespaceTest {
|
||||
assertEquals(DataPolicy.EMPTY, TestUtils.readField("dataPolicy", fb));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void testComplexClient() throws Exception {
|
||||
assertTrue(context.containsBean("complex"));
|
||||
ClientRegionFactoryBean fb = context.getBean("&complex", ClientRegionFactoryBean.class);
|
||||
@@ -135,4 +140,33 @@ public class ClientRegionNamespaceTest {
|
||||
ObjectSizer sizer = evicAttr.getObjectSizer();
|
||||
assertEquals(SimpleObjectSizer.class, sizer.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientRegionWithCacheLoaderAndCacheWriter() throws Exception {
|
||||
assertTrue(context.containsBean("loadWithWrite"));
|
||||
|
||||
ClientRegionFactoryBean factory = context.getBean("&loadWithWrite", ClientRegionFactoryBean.class);
|
||||
|
||||
assertNotNull(factory);
|
||||
assertEquals(ClientRegionShortcut.LOCAL, TestUtils.readField("shortcut", factory));
|
||||
assertTrue(TestUtils.readField("cacheLoader", factory) instanceof TestCacheLoader);
|
||||
assertTrue(TestUtils.readField("cacheWriter", factory) instanceof TestCacheWriter);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static final class TestCacheLoader implements CacheLoader<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object load(final LoaderHelper<Object, Object> helper) throws CacheLoaderException {
|
||||
throw new UnsupportedOperationException("Not Implemented!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TestCacheWriter extends CacheWriterAdapter<Object, Object> {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
||||
">
|
||||
|
||||
<gfe:client-cache/>
|
||||
|
||||
<gfe:pool>
|
||||
<gfe:server host="localhost" port="54321"/>
|
||||
</gfe:pool>
|
||||
|
||||
<bean id="localCacheLoader" class="org.springframework.data.gemfire.client.ClientRegionWithCacheLoaderWriterTest$LocalAppDataCacheLoader"/>
|
||||
<bean id="localCacheWriter" class="org.springframework.data.gemfire.client.ClientRegionWithCacheLoaderWriterTest$LocalAppDataCacheWriter"/>
|
||||
|
||||
<gfe:client-region id="localAppDataRegion" name="LocalAppData" shortcut="LOCAL">
|
||||
<gfe:cache-loader>
|
||||
<ref bean="localCacheLoader"/>
|
||||
</gfe:cache-loader>
|
||||
<gfe:cache-writer>
|
||||
<ref bean="localCacheWriter"/>
|
||||
</gfe:cache-writer>
|
||||
</gfe:client-region>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
||||
">
|
||||
|
||||
<gfe:cache/>
|
||||
<gfe:cache-server auto-startup="true" port="54321"/>
|
||||
<gfe:local-region id="localAppDataRegion" name="LocalAppData"/>
|
||||
|
||||
</beans>
|
||||
@@ -43,5 +43,14 @@
|
||||
</gfe:object-sizer>
|
||||
</gfe:eviction>
|
||||
</gfe:client-region>
|
||||
|
||||
|
||||
<gfe:client-region id="loadWithWrite" name="LoadedWriterRegion" shortcut="LOCAL">
|
||||
<gfe:cache-loader>
|
||||
<bean class="org.springframework.data.gemfire.config.ClientRegionNamespaceTest$TestCacheLoader"/>
|
||||
</gfe:cache-loader>
|
||||
<gfe:cache-writer>
|
||||
<bean class="org.springframework.data.gemfire.config.ClientRegionNamespaceTest$TestCacheWriter"/>
|
||||
</gfe:cache-writer>
|
||||
</gfe:client-region>
|
||||
|
||||
</beans>
|
||||
|
||||
Reference in New Issue
Block a user