SGF-196 - Support adding CacheListeners, CacheLoaders and CacheWriters, along with other mutable Region attributes to an existing Region.

This commit is contained in:
John Blum
2015-04-07 19:27:15 -07:00
parent b9881dfad9
commit b42ca233f0
9 changed files with 959 additions and 35 deletions

View File

@@ -16,15 +16,217 @@
package org.springframework.data.gemfire;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import com.gemstone.gemfire.cache.AttributesMutator;
import com.gemstone.gemfire.cache.CacheListener;
import com.gemstone.gemfire.cache.CacheLoader;
import com.gemstone.gemfire.cache.CacheWriter;
import com.gemstone.gemfire.cache.CustomExpiry;
import com.gemstone.gemfire.cache.EvictionAttributesMutator;
import com.gemstone.gemfire.cache.ExpirationAttributes;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.asyncqueue.AsyncEventQueue;
import com.gemstone.gemfire.cache.wan.GatewaySender;
/**
* The LookupRegionFactoryBean class is a concrete implementation of RegionLookupFactoryBean for handling &
* gt;gfe:lookup-region/&lt SDG XML namespace (XSD) elements.
* The LookupRegionFactoryBean class is a concrete implementation of RegionLookupFactoryBean for handling
* >gfe:lookup-region/< SDG XML namespace (XSD) elements.
*
* @author John Blum
* @see org.springframework.data.gemfire.RegionLookupFactoryBean
* @see com.gemstone.gemfire.cache.AttributesMutator
* @since 1.6.0
*/
@SuppressWarnings("unused")
public class LookupRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V> {
private Boolean cloningEnabled;
private Boolean enableStatistics;
private AsyncEventQueue[] asyncEventQueues;
private CacheListener<K, V>[] cacheListeners;
private CacheLoader<K, V> cacheLoader;
private CacheWriter<K, V> cacheWriter;
private CustomExpiry<K, V> customEntryIdleTimeout;
private CustomExpiry<K, V> customEntryTimeToLive;
private ExpirationAttributes entryIdleTimeout;
private ExpirationAttributes entryTimeToLive;
private ExpirationAttributes regionIdleTimeout;
private ExpirationAttributes regionTimeToLive;
private GatewaySender[] gatewaySenders;
private Integer evictionMaximum;
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
AttributesMutator<K, V> attributesMutator = getRegion().getAttributesMutator();
if (!ObjectUtils.isEmpty(asyncEventQueues)) {
for (AsyncEventQueue asyncEventQueue : asyncEventQueues) {
attributesMutator.addAsyncEventQueueId(asyncEventQueue.getId());
}
}
if (!ObjectUtils.isEmpty(cacheListeners)) {
for (CacheListener<K, V> cacheListener : cacheListeners) {
attributesMutator.addCacheListener(cacheListener);
}
}
if (cacheLoader != null) {
attributesMutator.setCacheLoader(cacheLoader);
}
if (cacheWriter != null) {
attributesMutator.setCacheWriter(cacheWriter);
}
if (cloningEnabled != null) {
attributesMutator.setCloningEnabled(cloningEnabled);
}
if (isStatisticsEnabled()) {
assertStatisticsEnabled();
if (customEntryIdleTimeout != null) {
attributesMutator.setCustomEntryIdleTimeout(customEntryIdleTimeout);
}
if (customEntryTimeToLive != null) {
attributesMutator.setCustomEntryTimeToLive(customEntryTimeToLive);
}
if (entryIdleTimeout != null) {
attributesMutator.setEntryIdleTimeout(entryIdleTimeout);
}
if (entryTimeToLive != null) {
attributesMutator.setEntryTimeToLive(entryTimeToLive);
}
if (regionIdleTimeout != null) {
attributesMutator.setRegionIdleTimeout(regionIdleTimeout);
}
if (regionTimeToLive != null) {
attributesMutator.setRegionTimeToLive(regionTimeToLive);
}
}
if (evictionMaximum != null) {
EvictionAttributesMutator evictionAttributesMutator = attributesMutator.getEvictionAttributesMutator();
evictionAttributesMutator.setMaximum(evictionMaximum);
}
if (!ObjectUtils.isEmpty(gatewaySenders)) {
for (GatewaySender gatewaySender : gatewaySenders) {
attributesMutator.addGatewaySenderId(gatewaySender.getId());
}
}
}
@Override
final boolean isLookupEnabled() {
return true;
}
/* (non-Javadoc) */
public void setAsyncEventQueues(AsyncEventQueue[] asyncEventQueues) {
this.asyncEventQueues = asyncEventQueues;
}
/* (non-Javadoc) */
public void setCacheListeners(CacheListener<K, V>[] cacheListeners) {
this.cacheListeners = cacheListeners;
}
/* (non-Javadoc) */
public void setCacheLoader(CacheLoader<K, V> cacheLoader) {
this.cacheLoader = cacheLoader;
}
/* (non-Javadoc) */
public void setCacheWriter(CacheWriter<K, V> cacheWriter) {
this.cacheWriter = cacheWriter;
}
/* (non-Javadoc) */
public void setCloningEnabled(Boolean cloningEnabled) {
this.cloningEnabled = cloningEnabled;
}
/* (non-Javadoc) */
public void setCustomEntryIdleTimeout(CustomExpiry<K, V> customEntryIdleTimeout) {
setStatisticsEnabled(customEntryIdleTimeout != null);
this.customEntryIdleTimeout = customEntryIdleTimeout;
}
/* (non-Javadoc) */
public void setCustomEntryTimeToLive(CustomExpiry<K, V> customEntryTimeToLive) {
setStatisticsEnabled(customEntryTimeToLive != null);
this.customEntryTimeToLive = customEntryTimeToLive;
}
/* (non-Javadoc) */
public void setEntryIdleTimeout(ExpirationAttributes entryIdleTimeout) {
setStatisticsEnabled(entryIdleTimeout != null);
this.entryIdleTimeout = entryIdleTimeout;
}
/* (non-Javadoc) */
public void setEntryTimeToLive(ExpirationAttributes entryTimeToLive) {
setStatisticsEnabled(entryTimeToLive != null);
this.entryTimeToLive = entryTimeToLive;
}
/* (non-Javadoc) */
public void setEvictionMaximum(final Integer evictionMaximum) {
this.evictionMaximum = evictionMaximum;
}
/* (non-Javadoc) */
public void setGatewaySenders(GatewaySender[] gatewaySenders) {
this.gatewaySenders = gatewaySenders;
}
/* (non-Javadoc) */
public void setRegionIdleTimeout(ExpirationAttributes regionIdleTimeout) {
setStatisticsEnabled(regionIdleTimeout != null);
this.regionIdleTimeout = regionIdleTimeout;
}
/* (non-Javadoc) */
public void setRegionTimeToLive(ExpirationAttributes regionTimeToLive) {
setStatisticsEnabled(regionTimeToLive != null);
this.regionTimeToLive = regionTimeToLive;
}
/* (non-Javadoc) */
public void setStatisticsEnabled(Boolean enableStatistics) {
this.enableStatistics = enableStatistics;
}
/* (non-Javadoc) */
protected boolean isStatisticsEnabled() {
return Boolean.TRUE.equals(this.enableStatistics);
}
/* (non-Javadoc) */
private void assertStatisticsEnabled() {
Region localRegion = getRegion();
Assert.state(localRegion.getAttributes().getStatisticsEnabled(), String.format(
"Statistics for Region '%1$s' must be enabled to change Entry & Region TTL/TTI Expiration settings",
localRegion.getFullPath()));
}
}

View File

@@ -29,11 +29,15 @@ import com.gemstone.gemfire.cache.GemFireCache;
import com.gemstone.gemfire.cache.Region;
/**
* Simple FactoryBean for retrieving generic GemFire {@link Region}s. If the Region does not exist,
* an exception is thrown. For declaring and configuring new regions, see {@link RegionFactoryBean}.
* 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}.
*
* @author Costin Leau
* @author John Blum
* @see org.springframework.beans.factory.BeanNameAware
* @see org.springframework.beans.factory.FactoryBean
* @see org.springframework.beans.factory.InitializingBean
* @see com.gemstone.gemfire.cache.Region
*/
@SuppressWarnings("unused")
public abstract class RegionLookupFactoryBean<K, V> implements FactoryBean<Region<K, V>>, InitializingBean, BeanNameAware {
@@ -45,19 +49,19 @@ public abstract class RegionLookupFactoryBean<K, V> implements FactoryBean<Regio
private GemFireCache cache;
private Region<?, ?> parent;
private Region<K, V> region;
private volatile Region<K, V> region;
private String beanName;
private String name;
private String regionName;
public void afterPropertiesSet() throws Exception {
Assert.notNull(cache, "The 'cache' property must be set.");
Assert.notNull(cache, "the 'cache' reference property must be set");
String regionName = (StringUtils.hasText(this.regionName) ? this.regionName
: (StringUtils.hasText(name) ? name : beanName));
Assert.hasText(regionName, "The 'regionName', 'name' or 'beanName' property must be set.");
Assert.hasText(regionName, "'regionName', 'name' or 'beanName' property must be set");
synchronized (cache) {
//region = (getParent() != null ? getParent().getSubregion(regionName) : cache.getRegion(regionName));
@@ -71,7 +75,7 @@ public abstract class RegionLookupFactoryBean<K, V> implements FactoryBean<Regio
}
if (region != null) {
log.info(String.format("Retrieved Region [%1$s] from 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);
@@ -93,11 +97,12 @@ public abstract class RegionLookupFactoryBean<K, V> implements FactoryBean<Regio
}
public Region<K, V> getObject() throws Exception {
return region;
return getRegion();
}
public Class<?> getObjectType() {
return (region != null ? region.getClass() : Region.class);
Region localRegion = getRegion();
return (localRegion != null ? localRegion.getClass() : Region.class);
}
public boolean isSingleton() {
@@ -158,6 +163,16 @@ public abstract class RegionLookupFactoryBean<K, V> implements FactoryBean<Regio
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.
@@ -170,20 +185,19 @@ public abstract class RegionLookupFactoryBean<K, V> implements FactoryBean<Regio
this.regionName = regionName;
}
private boolean isLookupEnabled() {
/* (non-Javadoc) */
boolean isLookupEnabled() {
return Boolean.TRUE.equals(getLookupEnabled());
}
public Boolean getLookupEnabled() {
return lookupEnabled;
}
/* (non-Javadoc) */
public void setLookupEnabled(Boolean lookupEnabled) {
this.lookupEnabled = lookupEnabled;
}
protected Region<K, V> getRegion() {
return region;
/* (non-Javadoc) */
public Boolean getLookupEnabled() {
return lookupEnabled;
}
}

View File

@@ -36,6 +36,9 @@ import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import com.gemstone.gemfire.cache.asyncqueue.AsyncEventQueue;
import com.gemstone.gemfire.cache.wan.GatewaySender;
/**
* Abstract base class encapsulating functionality common to all Region Parsers.
*
@@ -51,14 +54,14 @@ abstract class AbstractRegionParser extends AbstractSingleBeanDefinitionParser {
return getRegionFactoryClass();
}
protected abstract Class<?> getRegionFactoryClass();
@Override
protected String getParentName(final Element element) {
String regionTemplate = element.getAttribute("template");
return (StringUtils.hasText(regionTemplate) ? regionTemplate : super.getParentName(element));
}
protected abstract Class<?> getRegionFactoryClass();
protected boolean isRegionTemplate(final Element element) {
String localName = element.getLocalName();
return (localName != null && localName.endsWith("-template"));
@@ -135,25 +138,26 @@ abstract class AbstractRegionParser extends AbstractSingleBeanDefinitionParser {
ParsingUtils.setPropertyValue(element, regionBuilder, "hub-id");
parseCollectionOfCustomSubElements(element, parserContext, regionBuilder,
"com.gemstone.gemfire.cache.asyncqueue.AsyncEventQueue", "async-event-queue", "asyncEventQueues");
parseCollectionOfCustomSubElements(element, parserContext, regionBuilder,
"com.gemstone.gemfire.cache.wan.GatewaySender", "gateway-sender","gatewaySenders");
parseCollectionOfCustomSubElements(element, parserContext, regionBuilder, AsyncEventQueue.class.getName(),
"async-event-queue", "asyncEventQueues");
parseCollectionOfCustomSubElements(element, parserContext, regionBuilder, GatewaySender.class.getName(),
"gateway-sender", "gatewaySenders");
List<Element> subElements = DomUtils.getChildElements(element);
for (Element subElement : subElements) {
if (subElement.getLocalName().equals("cache-listener")) {
regionBuilder.addPropertyValue("cacheListeners",
ParsingUtils.parseRefOrNestedBeanDeclaration(parserContext, subElement, regionBuilder));
regionBuilder.addPropertyValue("cacheListeners", ParsingUtils.parseRefOrNestedBeanDeclaration(
parserContext, subElement, regionBuilder));
}
else if (subElement.getLocalName().equals("cache-loader")) {
regionBuilder.addPropertyValue("cacheLoader",
ParsingUtils.parseRefOrSingleNestedBeanDeclaration(parserContext, subElement, regionBuilder));
regionBuilder.addPropertyValue("cacheLoader", ParsingUtils.parseRefOrSingleNestedBeanDeclaration(
parserContext, subElement, regionBuilder));
}
else if (subElement.getLocalName().equals("cache-writer")) {
regionBuilder.addPropertyValue("cacheWriter",
ParsingUtils.parseRefOrSingleNestedBeanDeclaration(parserContext, subElement, regionBuilder));
regionBuilder.addPropertyValue("cacheWriter", ParsingUtils.parseRefOrSingleNestedBeanDeclaration(
parserContext, subElement, regionBuilder));
}
}
@@ -200,7 +204,7 @@ abstract class AbstractRegionParser extends AbstractSingleBeanDefinitionParser {
return (regionAttributes instanceof BeanDefinition ? (BeanDefinition) regionAttributes : null);
}
private void parseCollectionOfCustomSubElements(Element element, ParserContext parserContext,
protected void parseCollectionOfCustomSubElements(Element element, ParserContext parserContext,
BeanDefinitionBuilder builder, String className, String subElementName, String propertyName) {
List<Element> subElements = DomUtils.getChildElementsByTagName(element, subElementName,
subElementName + "-ref");

View File

@@ -19,14 +19,20 @@ package org.springframework.data.gemfire.config;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.gemfire.LookupRegionFactoryBean;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import com.gemstone.gemfire.cache.asyncqueue.AsyncEventQueue;
import com.gemstone.gemfire.cache.wan.GatewaySender;
/**
* Parser for &lt;lookup-region;gt; definitions.
* Parser for GFE &lt;lookup-region&gt; bean definitions.
*
* @author Costin Leau
* @author David Turanski
* @author John Blum
* @see org.springframework.data.gemfire.LookupRegionFactoryBean
* @see org.springframework.data.gemfire.config.AbstractRegionParser
*/
class LookupRegionParser extends AbstractRegionParser {
@@ -38,12 +44,44 @@ class LookupRegionParser extends AbstractRegionParser {
@Override
protected void doParseRegion(Element element, ParserContext parserContext, BeanDefinitionBuilder builder,
boolean subRegion) {
super.doParse(element, builder);
String resolvedCacheRef = ParsingUtils.resolveCacheReference(element.getAttribute("cache-ref"));
builder.addPropertyReference("cache", resolvedCacheRef);
ParsingUtils.setPropertyValue(element, builder, "name", "name");
ParsingUtils.setPropertyValue(element, builder, "cloning-enabled");
ParsingUtils.setPropertyValue(element, builder, "eviction-maximum");
ParsingUtils.setPropertyValue(element, builder, "name");
ParsingUtils.parseExpiration(parserContext, element, builder);
parseCollectionOfCustomSubElements(element, parserContext, builder, AsyncEventQueue.class.getName(),
"async-event-queue", "asyncEventQueues");
parseCollectionOfCustomSubElements(element, parserContext, builder, GatewaySender.class.getName(),
"gateway-sender", "gatewaySenders");
Element cacheListenerElement = DomUtils.getChildElementByTagName(element, "cache-listener");
if (cacheListenerElement != null) {
builder.addPropertyValue("cacheListeners", ParsingUtils.parseRefOrNestedBeanDeclaration(parserContext,
cacheListenerElement, builder));
}
Element cacheLoaderElement = DomUtils.getChildElementByTagName(element, "cache-loader");
if (cacheLoaderElement != null) {
builder.addPropertyValue("cacheLoader", ParsingUtils.parseRefOrSingleNestedBeanDeclaration(
parserContext, cacheLoaderElement, builder));
}
Element cacheWriterElement = DomUtils.getChildElementByTagName(element, "cache-writer");
if (cacheWriterElement != null) {
builder.addPropertyValue("cacheWriter", ParsingUtils.parseRefOrSingleNestedBeanDeclaration(
parserContext, cacheWriterElement, builder));
}
if (!subRegion) {
parseSubRegions(element, parserContext, resolvedCacheRef);

View File

@@ -448,7 +448,174 @@ Defines a lookup Subregion
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="basicRegionType">
<xsd:group ref="subRegionGroup" minOccurs="0" maxOccurs="unbounded" />
<xsd:sequence>
<xsd:element name="cache-listener" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation source="com.gemstone.gemfire.cache.CacheListener"><![CDATA[
A cache listener definition for this region. A cache listener handles region or entry related events (that occur after
various operations on the region). Multiple listeners can be declared in a nested manner.
Note: Avoid the risk of deadlock. Since the listener is invoked while holding a lock on the entry generating the event,
it is easy to generate a deadlock by interacting with the region. For this reason, it is highly recommended to use some
other thread for accessing the region and not waiting for it to complete its task.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="com.gemstone.gemfire.cache.CacheListener" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:any namespace="##other" processContents="skip" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Inner bean definition of the cache listener.
]]></xsd:documentation>
</xsd:annotation>
</xsd:any>
</xsd:sequence>
<xsd:attribute name="ref" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the cache listener bean referred by this declaration. Used as a convenience method. If no reference exists,
use inner bean declarations.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<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:element name="region-ttl" type="expirationType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[[
Time to live configuration for the region itself. Default: no expiration.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="region-tti" type="expirationType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[[
Time to idle (or idle timeout) configuration for the region itself. Default: no expiration.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:choice>
<xsd:element name="entry-ttl" type="expirationType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[[
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>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="com.gemstone.gemfire.cache.CustomExpiry" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation><![CDATA[[
CustomExpiry Time-to-Live (TTL) configuration for the Region Entries. The default is no expiration.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
<xsd:choice>
<xsd:element name="entry-tti" type="expirationType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[[
Time to idle (or idle timeout) configuration for the region entries. Default: no expiration.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="custom-entry-tti" type="customExpirationType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="com.gemstone.gemfire.cache.CustomExpiry" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation><![CDATA[[
CustomExpiry Time-to-Idle (or Idle Timeout, TTI) configuration for the Region entries. The default is no expiration.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="gateway-sender" type="baseGatewaySenderType"/>
<xsd:element name="gateway-sender-ref">
<xsd:complexType>
<xsd:attribute name="bean" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the gateway sender bean referred by this declaration. Used as a convenience method. If no reference exists,
use inner bean declarations.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="async-event-queue" type="baseAsyncEventQueueType"/>
<xsd:element name="async-event-queue-ref">
<xsd:complexType>
<xsd:attribute name="bean" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the gateway sender bean referred by this declaration. Used as a convenience method. If no reference exists,
use inner bean declarations.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:choice>
<xsd:group ref="subRegionGroup" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="cloning-enabled" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[[
Determines how fromDelta applies deltas to the local cache for delta propagation. When true, the updates are applied
to a clone of the value and then the clone is saved to the cache. When false, the value is modified in place
in the cache. GemFire default is false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="eviction-maximum" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[[
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -589,8 +756,7 @@ CustomExpiry Time-to-Live (TTL) configuration for the Region Entries. The defaul
</xsd:element>
</xsd:choice>
<xsd:choice>
<xsd:element name="entry-tti" type="expirationType"
minOccurs="0" maxOccurs="1">
<xsd:element name="entry-tti" type="expirationType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[[
Time to idle (or idle timeout) configuration for the region entries. Default: no expiration.

View File

@@ -0,0 +1,209 @@
/*
* 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Test;
import com.gemstone.gemfire.cache.AttributesMutator;
import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.CacheListener;
import com.gemstone.gemfire.cache.CacheLoader;
import com.gemstone.gemfire.cache.CacheWriter;
import com.gemstone.gemfire.cache.CustomExpiry;
import com.gemstone.gemfire.cache.EvictionAttributesMutator;
import com.gemstone.gemfire.cache.ExpirationAttributes;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.RegionAttributes;
import com.gemstone.gemfire.cache.asyncqueue.AsyncEventQueue;
import com.gemstone.gemfire.cache.wan.GatewaySender;
/**
* The LookupRegionFactoryBeanTest class is a test suite of test cases testing the contract and functionality
* of the LookupRegionFactoryBean class.
*
* @author John Blum
* @see org.junit.Test
* @see org.mockito.Mockito
* @see org.springframework.data.gemfire.LookupRegionFactoryBean
* @see com.gemstone.gemfire.cache.AttributesMutator
* @see com.gemstone.gemfire.cache.Cache
* @see com.gemstone.gemfire.cache.EvictionAttributesMutator
* @see com.gemstone.gemfire.cache.Region
* @since 1.7.0
*/
public class LookupRegionFactoryBeanTest {
protected AsyncEventQueue mockAsyncEventQueue(final String id) {
AsyncEventQueue mockQueue = mock(AsyncEventQueue.class, String.format("MockAsyncEventQueue.%1$s", id));
when(mockQueue.getId()).thenReturn(id);
return mockQueue;
}
protected GatewaySender mockGatewaySender(final String id) {
GatewaySender mockGatewaySender = mock(GatewaySender.class, String.format("MockGatewaySender.%1$s", id));
when(mockGatewaySender.getId()).thenReturn(id);
return mockGatewaySender;
}
@Test
@SuppressWarnings("unchecked")
public void testAfterPropertiesSet() throws Exception {
Cache mockCache = mock(Cache.class, "testAfterPropertiesSet.MockCache");
Region<Object, Object> mockRegion = mock(Region.class, "testAfterPropertiesSet.MockRegion");
RegionAttributes<Object, Object> mockRegionAttributes = mock(RegionAttributes.class,
"testAfterPropertiesSet.MockRegionAttributes");
EvictionAttributesMutator mockEvictionAttributesMutator = mock(EvictionAttributesMutator.class,
"testAfterPropertiesSet.EvictionAttributesMutator");
AttributesMutator<Object, Object> mockAttributesMutator = mock(AttributesMutator.class,
"testAfterPropertiesSet.MockAttributesMutator");
when(mockCache.getRegion(eq("Example"))).thenReturn(mockRegion);
when(mockRegion.getFullPath()).thenReturn("/Example");
when(mockRegion.getName()).thenReturn("Example");
when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes);
when(mockRegionAttributes.getStatisticsEnabled()).thenReturn(true);
when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator);
when(mockAttributesMutator.getEvictionAttributesMutator()).thenReturn(mockEvictionAttributesMutator);
AsyncEventQueue mockAsyncEventQueueOne = mockAsyncEventQueue("AEQ1");
AsyncEventQueue mockAsyncEventQueueTwo = mockAsyncEventQueue("AEQ2");
CacheListener mockCacheListenerZero = mock(CacheListener.class, "testAfterPropertiesSet.MockCacheListener.0");
CacheListener mockCacheListenerOne = mock(CacheListener.class, "testAfterPropertiesSet.MockCacheListener.1");
CacheListener mockCacheListenerTwo = mock(CacheListener.class, "testAfterPropertiesSet.MockCacheListener.2");
CacheLoader mockCacheLoader = mock(CacheLoader.class, "testAfterPropertiesSet.MockCacheLoader");
CacheWriter mockCacheWriter = mock(CacheWriter.class, "testAfterPropertiesSet.MockCacheWriter");
CustomExpiry mockCustomExpiryTti = mock(CustomExpiry.class, "testAfterPropertiesSet.MockCustomExpiry.TTI");
CustomExpiry mockCustomExpiryTtl = mock(CustomExpiry.class, "testAfterPropertiesSet.MockCustomExpiry.TTL");
ExpirationAttributes mockExpirationAttributesEntryTti = mock(ExpirationAttributes.class,
"testAfterPropertiesSet.MockExpirationAttributes.Entry.TTI");
ExpirationAttributes mockExpirationAttributesEntryTtl = mock(ExpirationAttributes.class,
"testAfterPropertiesSet.MockExpirationAttributes.Entry.TTL");
ExpirationAttributes mockExpirationAttributesRegionTti = mock(ExpirationAttributes.class,
"testAfterPropertiesSet.MockExpirationAttributes.Region.TTI");
ExpirationAttributes mockExpirationAttributesRegionTtl = mock(ExpirationAttributes.class,
"testAfterPropertiesSet.MockExpirationAttributes.Region.TTL");
GatewaySender mockGatewaySender = mockGatewaySender("GWS1");
LookupRegionFactoryBean factoryBean = new LookupRegionFactoryBean();
factoryBean.setAsyncEventQueues(new AsyncEventQueue[] { mockAsyncEventQueueOne, mockAsyncEventQueueTwo });
factoryBean.setBeanName("Example");
factoryBean.setCache(mockCache);
factoryBean.setCacheLoader(mockCacheLoader);
factoryBean.setCacheWriter(mockCacheWriter);
factoryBean.setCloningEnabled(true);
factoryBean.setCustomEntryIdleTimeout(mockCustomExpiryTti);
factoryBean.setCustomEntryTimeToLive(mockCustomExpiryTtl);
factoryBean.setEntryIdleTimeout(mockExpirationAttributesEntryTti);
factoryBean.setEntryTimeToLive(mockExpirationAttributesEntryTtl);
factoryBean.setGatewaySenders(new GatewaySender[] { mockGatewaySender });
factoryBean.setEvictionMaximum(1000);
factoryBean.setRegionIdleTimeout(mockExpirationAttributesRegionTti);
factoryBean.setRegionTimeToLive(mockExpirationAttributesRegionTtl);
factoryBean.setStatisticsEnabled(true);
factoryBean.setCacheListeners(new CacheListener[] {
mockCacheListenerZero, mockCacheListenerOne, mockCacheListenerTwo
});
factoryBean.afterPropertiesSet();
verify(mockAttributesMutator, times(1)).addAsyncEventQueueId(eq("AEQ1"));
verify(mockAttributesMutator, times(1)).addAsyncEventQueueId(eq("AEQ2"));
verify(mockAttributesMutator, times(1)).addCacheListener(same(mockCacheListenerZero));
verify(mockAttributesMutator, times(1)).addCacheListener(same(mockCacheListenerOne));
verify(mockAttributesMutator, times(1)).addCacheListener(same(mockCacheListenerTwo));
verify(mockAttributesMutator, times(1)).setCacheLoader(same(mockCacheLoader));
verify(mockAttributesMutator, times(1)).setCacheWriter(same(mockCacheWriter));
verify(mockAttributesMutator, times(1)).setCloningEnabled(eq(true));
verify(mockAttributesMutator, times(1)).setCustomEntryIdleTimeout(same(mockCustomExpiryTti));
verify(mockAttributesMutator, times(1)).setCustomEntryTimeToLive(same(mockCustomExpiryTtl));
verify(mockAttributesMutator, times(1)).setEntryIdleTimeout(same(mockExpirationAttributesEntryTti));
verify(mockAttributesMutator, times(1)).setEntryTimeToLive(same(mockExpirationAttributesEntryTtl));
verify(mockAttributesMutator, times(1)).addGatewaySenderId(eq("GWS1"));
verify(mockEvictionAttributesMutator, times(1)).setMaximum(eq(1000));
verify(mockAttributesMutator, times(1)).setRegionIdleTimeout(same(mockExpirationAttributesRegionTti));
verify(mockAttributesMutator, times(1)).setRegionTimeToLive(same(mockExpirationAttributesRegionTtl));
}
@Test(expected = IllegalStateException.class)
@SuppressWarnings("unchecked")
public void testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified() throws Exception {
Cache mockCache = mock(Cache.class, "testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified.MockCache");
Region<Object, Object> mockRegion = mock(Region.class, "testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified.MockRegion");
RegionAttributes<Object, Object> mockRegionAttributes = mock(RegionAttributes.class,
"testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified.MockRegionAttributes");
AttributesMutator mockAttributesMutator = mock(AttributesMutator.class,
"testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified.MockAttributesMutator");
ExpirationAttributes mockExpirationAttributesEntryTtl = mock(ExpirationAttributes.class,
"testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified.MockExpirationAttributes.Entry.TTL");
when(mockCache.getRegion(eq("Example"))).thenReturn(mockRegion);
when(mockRegion.getFullPath()).thenReturn("/Example");
when(mockRegion.getName()).thenReturn("Example");
when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes);
when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator);
when(mockRegionAttributes.getStatisticsEnabled()).thenReturn(false);
LookupRegionFactoryBean factoryBean = new LookupRegionFactoryBean();
factoryBean.setBeanName("Example");
factoryBean.setCache(mockCache);
factoryBean.setEntryTimeToLive(mockExpirationAttributesEntryTtl);
//factoryBean.setStatisticsEnabled(true);
assertTrue(factoryBean.isStatisticsEnabled());
try {
factoryBean.afterPropertiesSet();
}
catch (IllegalStateException expected) {
assertEquals("Statistics for Region '/Example' must be enabled to change Entry & Region TTL/TTI Expiration settings",
expected.getMessage());
throw expected;
}
finally {
verify(mockAttributesMutator, never()).setEntryTimeToLive(any(ExpirationAttributes.class));
}
}
}

View File

@@ -0,0 +1,230 @@
/*
* 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.gemstone.gemfire.cache.CacheListener;
import com.gemstone.gemfire.cache.CacheLoader;
import com.gemstone.gemfire.cache.CacheLoaderException;
import com.gemstone.gemfire.cache.CacheWriter;
import com.gemstone.gemfire.cache.CacheWriterException;
import com.gemstone.gemfire.cache.CustomExpiry;
import com.gemstone.gemfire.cache.DataPolicy;
import com.gemstone.gemfire.cache.EntryEvent;
import com.gemstone.gemfire.cache.ExpirationAction;
import com.gemstone.gemfire.cache.ExpirationAttributes;
import com.gemstone.gemfire.cache.LoaderHelper;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.RegionEvent;
import com.gemstone.gemfire.cache.asyncqueue.AsyncEvent;
import com.gemstone.gemfire.cache.asyncqueue.AsyncEventListener;
import com.gemstone.gemfire.cache.util.CacheListenerAdapter;
import com.gemstone.gemfire.management.internal.cli.util.spring.StringUtils;
/**
* The LookupRegionMutationIntegrationTest class is a test suite of test cases testing the contract and integrated
* functionality between natively-defined GemFire Cache Regions and SDG's Region lookup functionality combined with
* Region attribute(s) mutation.
*
* @author John Blum
* @see org.junit.Test
* @see org.junit.runner.RunWith
* @see org.springframework.data.gemfire.LookupRegionFactoryBean
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
* @since 1.7.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@SuppressWarnings("unused")
public class LookupRegionMutationIntegrationTest {
@Resource(name = "Example")
private Region<?, ?> example;
protected void assertGemFireComponent(Object gemfireComponent, String expectedName) {
assertNotNull("The GemFire component must not be null!", gemfireComponent);
assertEquals(expectedName, gemfireComponent.toString());
}
protected void assertExpirationAttributes(ExpirationAttributes expirationAttributes,
String description, int expectedTimeout, ExpirationAction expectedAction) {
assertNotNull(String.format("ExpirationAttributes for '%1$s' must not be null!", description), expirationAttributes);
assertEquals(expectedAction, expirationAttributes.getAction());
assertEquals(expectedTimeout, expirationAttributes.getTimeout());
}
protected void assertCacheListeners(CacheListener[] cacheListeners, Collection<String> expectedCacheListenerNames) {
if (!expectedCacheListenerNames.isEmpty()) {
assertNotNull("CacheListeners must not be null!", cacheListeners);
assertEquals(expectedCacheListenerNames.size(), cacheListeners.length);
assertTrue(toStrings(cacheListeners).containsAll(expectedCacheListenerNames));
}
}
protected Collection<String> toStrings(Object[] objects) {
List<String> cacheListenerNames = new ArrayList<String>(objects.length);
for (Object object : objects) {
cacheListenerNames.add(object.toString());
}
return cacheListenerNames;
}
@Test
public void testRegionConfiguration() {
assertNotNull("'/Example' Region was not properly initialized!", example);
assertEquals("Example", example.getName());
assertEquals("/Example", example.getFullPath());
assertNotNull(example.getAttributes());
assertEquals(DataPolicy.REPLICATE, example.getAttributes().getDataPolicy());
assertEquals(13, example.getAttributes().getInitialCapacity());
assertEquals(0.85f, example.getAttributes().getLoadFactor(), 0.0f);
assertCacheListeners(example.getAttributes().getCacheListeners(), Arrays.asList("A", "B"));
assertGemFireComponent(example.getAttributes().getCacheLoader(), "C");
assertGemFireComponent(example.getAttributes().getCacheWriter(), "D");
assertExpirationAttributes(example.getAttributes().getRegionTimeToLive(), "Region TTL",
120, ExpirationAction.LOCAL_DESTROY);
assertExpirationAttributes(example.getAttributes().getRegionIdleTimeout(), "Region TTI",
60, ExpirationAction.INVALIDATE);
assertExpirationAttributes(example.getAttributes().getEntryTimeToLive(), "Entry TTL",
30, ExpirationAction.DESTROY);
assertGemFireComponent(example.getAttributes().getCustomEntryIdleTimeout(), "E");
assertNotNull(example.getAttributes().getGatewaySenderIds());
assertEquals(1, example.getAttributes().getGatewaySenderIds().size());
assertEquals("GWS", example.getAttributes().getGatewaySenderIds().iterator().next());
assertNotNull(example.getAttributes().getAsyncEventQueueIds());
assertEquals(1, example.getAttributes().getAsyncEventQueueIds().size());
assertEquals("AEQ", example.getAttributes().getAsyncEventQueueIds().iterator().next());
}
protected static interface Nameable extends BeanNameAware {
String getName();
void setName(String name);
}
protected static abstract class AbstractNameable implements Nameable {
private String name;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
@Override
public void setBeanName(final String name) {
if (!StringUtils.hasText(this.name)) {
setName(name);
}
}
@Override
public String toString() {
return getName();
}
}
public static final class TestAsyncEventListener extends AbstractNameable implements AsyncEventListener {
@Override public boolean processEvents(List<AsyncEvent> events) {
throw new UnsupportedOperationException("Not Implemented!");
}
@Override public void close() { }
}
public static final class TestCacheListener<K, V> extends CacheListenerAdapter<K, V> implements Nameable {
private String name;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
@Override
public void setBeanName(final String name) {
if (!StringUtils.hasText(this.name)) {
setName(name);
}
}
@Override
public String toString() {
return getName();
}
}
public static final class TestCacheLoader<K, V> extends AbstractNameable implements CacheLoader<K, V> {
@Override
public V load(LoaderHelper<K, V> helper) throws CacheLoaderException {
throw new UnsupportedOperationException("Not Implemented!");
}
@Override
public void close() { }
}
public static final class TestCacheWriter<K, V> extends AbstractNameable implements CacheWriter<K, V> {
@Override public void beforeUpdate(EntryEvent<K, V> event) throws CacheWriterException { }
@Override public void beforeCreate(EntryEvent<K, V> event) throws CacheWriterException { }
@Override public void beforeDestroy(EntryEvent<K, V> event) throws CacheWriterException { }
@Override public void beforeRegionDestroy(RegionEvent<K, V> event) throws CacheWriterException { }
@Override public void beforeRegionClear(RegionEvent<K, V> event) throws CacheWriterException { }
@Override public void close() { }
}
public static final class TestCustomExpiry<K, V> extends AbstractNameable implements CustomExpiry<K, V> {
@Override public ExpirationAttributes getExpiry(Region.Entry<K, V> entry) {
throw new UnsupportedOperationException("Not Implemented!");
}
@Override public void close() { }
}
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!DOCTYPE cache PUBLIC "-//GemStone Systems, Inc.//GemFire Declarative Caching 8.0//EN"
"http://www.gemstone.com/dtd/cache8_0.dtd">
<cache>
<region name="Example">
<region-attributes data-policy="replicate" cloning-enabled="false" initial-capacity="13" load-factor="0.85"
statistics-enabled="true">
<eviction-attributes>
<lru-entry-count action="overflow-to-disk" maximum="500"/>
</eviction-attributes>
</region-attributes>
</region>
</cache>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:gfe="http://www.springframework.org/schema/gemfire"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
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
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
">
<util:properties id="gemfireProperties">
<prop key="name">LookupRegionMutationIntegrationTest</prop>
<prop key="mcast-port">0</prop>
<prop key="log-level">warning</prop>
</util:properties>
<gfe:cache cache-xml-location="/lookup-region-mutation-cache.xml" properties-ref="gemfireProperties"/>
<bean id="B" class="org.springframework.data.gemfire.LookupRegionMutationIntegrationTest.TestCacheListener"/>
<gfe:lookup-region id="Example" cloning-enabled="true" eviction-maximum="1000">
<gfe:cache-listener>
<bean class="org.springframework.data.gemfire.LookupRegionMutationIntegrationTest.TestCacheListener" p:name="A"/>
<ref bean="B"/>
</gfe:cache-listener>
<gfe:cache-loader>
<bean class="org.springframework.data.gemfire.LookupRegionMutationIntegrationTest$TestCacheLoader" p:name="C"/>
</gfe:cache-loader>
<gfe:cache-writer>
<bean class="org.springframework.data.gemfire.LookupRegionMutationIntegrationTest$TestCacheWriter" p:name="D"/>
</gfe:cache-writer>
<gfe:region-ttl timeout="120" action="LOCAL_DESTROY"/>
<gfe:region-tti timeout="60" action="INVALIDATE"/>
<gfe:entry-ttl timeout="30" action="DESTROY"/>
<gfe:custom-entry-tti>
<bean class="org.springframework.data.gemfire.LookupRegionMutationIntegrationTest$TestCustomExpiry" p:name="E"/>
</gfe:custom-entry-tti>
<gfe:gateway-sender name="GWS" remote-distributed-system-id="123" manual-start="true"/>
<gfe:async-event-queue name="AEQ" persistent="false" parallel="true" dispatcher-threads="8">
<gfe:async-event-listener>
<bean class="org.springframework.data.gemfire.LookupRegionMutationIntegrationTest$TestAsyncEventListener" p:name="F"/>
</gfe:async-event-listener>
</gfe:async-event-queue>
</gfe:lookup-region>
</beans>