diff --git a/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java index 1e71f5a3..13932323 100644 --- a/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java @@ -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. + *

* @author Costin Leau * @author David Turanski + * @author John Blum */ public class ClientRegionFactoryBean extends RegionLookupFactoryBean 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 cacheListeners[]; - - private Interest[] interests; - - private String poolName; - private BeanFactory beanFactory; + private CacheListener[] cacheListeners; + + private CacheLoader cacheLoader; + + private CacheWriter cacheWriter; + private ClientRegionShortcut shortcut = null; private DataPolicy dataPolicy; + private Interest[] interests; + private RegionAttributes attributes; - private Region 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 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 factory = c.createClientRegionFactory(s); + ClientRegionFactory factory = ((ClientCache) cache).createClientRegionFactory(shortcut); // map the attributes onto the client if (attributes != null) { - CacheListener[] listeners = attributes.getCacheListeners(); - if (!ObjectUtils.isEmpty(listeners)) { - for (CacheListener listener : listeners) { - factory.addCacheListener(listener); - } - } factory.setCloningEnabled(attributes.getCloningEnabled()); factory.setConcurrencyLevel(attributes.getConcurrencyLevel()); factory.setCustomEntryIdleTimeout(attributes.getCustomEntryIdleTimeout()); @@ -157,11 +149,7 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean factory.setValueConstraint(attributes.getValueConstraint()); } - if (!ObjectUtils.isEmpty(cacheListeners)) { - for (CacheListener 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 extends RegionLookupFactoryBean } factory.setPoolName(poolName); - } else { + } + else { Pool pool = beanFactory.getBean(Pool.class); factory.setPoolName(pool.getName()); } @@ -182,35 +171,71 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean factory.setDiskStoreName(diskStoreName); } - Region reg = factory.create(regionName); + Region 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 factory) { + if (attributes != null) { + CacheListener[] listeners = attributes.getCacheListeners(); + + if (!ObjectUtils.isEmpty(listeners)) { + for (CacheListener listener : listeners) { + factory.addCacheListener(listener); + } + } + } + + if (!ObjectUtils.isEmpty(cacheListeners)) { + for (CacheListener listener : cacheListeners) { + factory.addCacheListener(listener); + } + } } protected void postProcess(Region region) { + registerInterests(region); + setCacheLoader(region); + setCacheWriter(region); + } + + private void registerInterests(final Region region) { if (!ObjectUtils.isEmpty(interests)) { for (Interest 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 region) { + if (cacheLoader != null) { + region.getAttributesMutator().setCacheLoader(this.cacheLoader); + } + } + + private void setCacheWriter(Region region) { + if (cacheWriter != null) { + region.getAttributesMutator().setCacheWriter(this.cacheWriter); + } + } + @Override public void destroy() throws Exception { Region region = getObject(); - // unregister interests + try { if (region != null && !ObjectUtils.isEmpty(interests)) { for (Interest interest : interests) { @@ -222,9 +247,9 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean } } } - // 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 extends RegionLookupFactoryBean 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 extends RegionLookupFactoryBean * 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. - * + *

* @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. - * + *

* @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 extends RegionLookupFactoryBean this.cacheListeners = cacheListeners; } + /** + * Sets the CacheLoader used to load data local to the client's Region on cache misses. + *

+ * @param cacheLoader a GemFire CacheLoader used to load data into the client Region. + * @see com.gemstone.gemfire.cache.CacheLoader + */ + public void setCacheLoader(CacheLoader cacheLoader) { + this.cacheLoader = cacheLoader; + } + + /** + * Sets the CacheWriter used to perform a synchronous write-behind when data is put into the client's Region. + *

+ * @param cacheWriter the GemFire CacheWriter used to perform synchronous write-behinds on put ops. + * @see com.gemstone.gemfire.cache.CacheWriter + */ + public void setCacheWriter(CacheWriter cacheWriter) { + this.cacheWriter = cacheWriter; + } + /** * Sets the data policy. Used only when a new region is created. * @@ -395,4 +442,5 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean public void setAttributes(RegionAttributes attributes) { this.attributes = attributes; } -} \ No newline at end of file + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/ClientRegionParser.java b/src/main/java/org/springframework/data/gemfire/config/ClientRegionParser.java index 7e61d11b..f54aebb1 100644 --- a/src/main/java/org/springframework/data/gemfire/config/ClientRegionParser.java +++ b/src/main/java/org/springframework/data/gemfire/config/ClientRegionParser.java @@ -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. - * + *

+ * To avoid eager evaluations, the region interests are declared as nested definition. + *

* @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 subElements = DomUtils.getChildElements(element); ManagedList interests = new ManagedList(); - // parse nested declarations - List 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"); } + } diff --git a/src/main/java/org/springframework/data/gemfire/config/ParsingUtils.java b/src/main/java/org/springframework/data/gemfire/config/ParsingUtils.java index fdfb591e..367e74d0 100644 --- a/src/main/java/org/springframework/data/gemfire/config/ParsingUtils.java +++ b/src/main/java/org/springframework/data/gemfire/config/ParsingUtils.java @@ -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. + *

* @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 { * * * - * @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 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 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 list = new ManagedList(); - 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. + *

+ * @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. + *

+ * @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. + *

+ * @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; } -} \ No newline at end of file +} diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.3.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.3.xsd index 31e500a2..23ca5de0 100755 --- a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.3.xsd +++ b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.3.xsd @@ -443,8 +443,7 @@ The name of the region definition.]]> - + @@ -502,7 +501,6 @@ Time to idle (or idle timeout) configuration for the region itself. Default: no ]]> - @@ -512,7 +510,6 @@ Time to live configuration for the region entries. Default: no expiration. ]]> - @@ -527,7 +524,6 @@ CustomExpiry time to live configuration for the region entries. Default: no expi - @@ -678,7 +674,6 @@ The fully qualified class name of the expected value type ]]> - @@ -806,7 +801,7 @@ use inner bean declarations. - + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/springframework/data/gemfire/client/ClientRegionFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/client/ClientRegionFactoryBeanTest.java index 751f8da7..83ccb5a9 100644 --- a/src/test/java/org/springframework/data/gemfire/client/ClientRegionFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/client/ClientRegionFactoryBeanTest.java @@ -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 fb = new ClientRegionFactoryBean(); + 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 factory = Mockito - .mock(ClientRegionFactory.class); - Mockito.when( - cache.createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY)) - .thenReturn(factory); + ClientRegionFactory factory = Mockito.mock(ClientRegionFactory.class); + Mockito.when(cache.createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY)).thenReturn(factory); @SuppressWarnings("unchecked") Region region = Mockito.mock(Region.class); @@ -61,4 +65,54 @@ public class ClientRegionFactoryBeanTest { assertSame(region, result); } + + @Test + public void testCloseDestroySettings() { + final ClientRegionFactoryBean factory = new ClientRegionFactoryBean(); + + 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 + } + } diff --git a/src/test/java/org/springframework/data/gemfire/client/ClientRegionWithCacheLoaderWriterTest.java b/src/test/java/org/springframework/data/gemfire/client/ClientRegionWithCacheLoaderWriterTest.java new file mode 100644 index 00000000..dfbb3b2b --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/client/ClientRegionWithCacheLoaderWriterTest.java @@ -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. + *

+ * @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 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 { + + private static final AtomicInteger VALUE_GENERATOR = new AtomicInteger(0); + + @Override + public Integer load(final LoaderHelper helper) throws CacheLoaderException { + return VALUE_GENERATOR.incrementAndGet(); + } + + @Override + public void close() { + } + } + + public static class LocalAppDataCacheWriter extends CacheWriterAdapter { + + private static final Map data = new ConcurrentHashMap(REGION_SIZE); + + @Override + public void beforeUpdate(final EntryEvent event) throws CacheWriterException { + data.put(event.getKey(), event.getNewValue()); + } + + public Integer get(final Integer key) { + return data.get(key); + } + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/client/MultipleClientCacheTest.java b/src/test/java/org/springframework/data/gemfire/client/MultipleClientCacheTest.java index b32beecd..939b1d85 100644 --- a/src/test/java/org/springframework/data/gemfire/client/MultipleClientCacheTest.java +++ b/src/test/java/org/springframework/data/gemfire/client/MultipleClientCacheTest.java @@ -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(); } + } diff --git a/src/test/java/org/springframework/data/gemfire/config/ClientRegionNamespaceTest.java b/src/test/java/org/springframework/data/gemfire/config/ClientRegionNamespaceTest.java index e9139cc3..2484c63c 100644 --- a/src/test/java/org/springframework/data/gemfire/config/ClientRegionNamespaceTest.java +++ b/src/test/java/org/springframework/data/gemfire/config/ClientRegionNamespaceTest.java @@ -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 { + + @Override + public Object load(final LoaderHelper helper) throws CacheLoaderException { + throw new UnsupportedOperationException("Not Implemented!"); + } + + @Override + public void close() { + } + } + + public static final class TestCacheWriter extends CacheWriterAdapter { + } + } diff --git a/src/test/resources/org/springframework/data/gemfire/client/clientcache-with-region-using-cache-loader-writer.xml b/src/test/resources/org/springframework/data/gemfire/client/clientcache-with-region-using-cache-loader-writer.xml new file mode 100644 index 00000000..b38c8df1 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/client/clientcache-with-region-using-cache-loader-writer.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/client/servercache-with-region-for-client.xml b/src/test/resources/org/springframework/data/gemfire/client/servercache-with-region-for-client.xml new file mode 100644 index 00000000..3873c44d --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/client/servercache-with-region-for-client.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/config/client-ns.xml b/src/test/resources/org/springframework/data/gemfire/config/client-ns.xml index cfc70c5a..ab1444da 100644 --- a/src/test/resources/org/springframework/data/gemfire/config/client-ns.xml +++ b/src/test/resources/org/springframework/data/gemfire/config/client-ns.xml @@ -43,5 +43,14 @@ - + + + + + + + + + +