From bb97756f8d62809df5e1f3d3aa68bda721ee1297 Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 9 Dec 2013 10:50:16 -0800 Subject: [PATCH] Fixes JIRA issue SGF-241 adding support for defining client sub-Regions using the gfe:client-region XML namespace element in an nested fashion. --- .../client/ClientRegionFactoryBean.java | 23 +++- .../gemfire/config/AbstractRegionParser.java | 35 +++--- .../gemfire/config/ClientRegionParser.java | 39 +++--- .../data/gemfire/config/ParsingUtils.java | 34 ++++-- .../gemfire/config/spring-gemfire-1.4.xsd | 89 ++++++++------ .../gemfire/client/ClientSubRegionTest.java | 115 ++++++++++++++++++ .../clientcache-with-subregion-config.xml | 25 ++++ .../servercache-with-region-for-client.xml | 4 + 8 files changed, 273 insertions(+), 91 deletions(-) create mode 100644 src/test/java/org/springframework/data/gemfire/client/ClientSubRegionTest.java create mode 100644 src/test/resources/org/springframework/data/gemfire/client/clientcache-with-subregion-config.xml 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 700a9e04..cae9cc72 100644 --- a/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java @@ -50,6 +50,7 @@ import com.gemstone.gemfire.internal.cache.GemFireCacheImpl; * @author David Turanski * @author John Blum */ +@SuppressWarnings("unused") public class ClientRegionFactoryBean extends RegionLookupFactoryBean implements BeanFactoryAware, DisposableBean { @@ -74,6 +75,8 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean private Interest[] interests; + private Region parent; + private RegionAttributes attributes; private Resource snapshot; @@ -96,7 +99,7 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean Assert.isTrue(((GemFireCacheImpl) cache).isClient(), "A client-cache instance is required."); } - final ClientCache clientCache = (ClientCache) cache; + ClientCache clientCache = (ClientCache) cache; ClientRegionFactory factory = clientCache.createClientRegionFactory(resolveClientRegionShortcut()); @@ -142,8 +145,18 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean factory.setDiskStoreName(diskStoreName); } - Region clientRegion = factory.create(regionName); - log.info("Created new cache region [" + regionName + "]"); + Region clientRegion = (this.parent != null ? factory.createSubregion(parent, regionName) + : factory.create(regionName)); + + if (log.isInfoEnabled()) { + if (parent != null) { + log.info(String.format("Created new Client Cache sub-Region [%1$s] under parent Region [%2$s].", + regionName, parent.getName())); + } + else { + log.info(String.format("Created new Client Cache Region [%1$s].", regionName)); + } + } if (snapshot != null) { clientRegion.loadSnapshot(snapshot.getInputStream()); @@ -481,6 +494,10 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean setDataPolicy(resolvedDataPolicy); } + public void setParent(Region parent) { + this.parent = parent; + } + protected boolean isPersistent() { return Boolean.TRUE.equals(persistent); } diff --git a/src/main/java/org/springframework/data/gemfire/config/AbstractRegionParser.java b/src/main/java/org/springframework/data/gemfire/config/AbstractRegionParser.java index 339882ae..6e09502d 100644 --- a/src/main/java/org/springframework/data/gemfire/config/AbstractRegionParser.java +++ b/src/main/java/org/springframework/data/gemfire/config/AbstractRegionParser.java @@ -35,9 +35,10 @@ import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; /** - * Base class for all Region Parsers - * + * Abstract base class encapsulating functionality common to all Region Parsers. + *

* @author David Turanski + * @author John Blum */ abstract class AbstractRegionParser extends AbstractSingleBeanDefinitionParser { @@ -68,9 +69,7 @@ abstract class AbstractRegionParser extends AbstractSingleBeanDefinitionParser { protected void doParseCommonRegionConfiguration(Element element, ParserContext parserContext, BeanDefinitionBuilder builder, BeanDefinitionBuilder regionAttributesBuilder, boolean subRegion) { - String cacheRef = element.getAttribute("cache-ref"); - String resolvedCacheRef = (StringUtils.hasText(cacheRef) ? cacheRef - : GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME); + String resolvedCacheRef = ParsingUtils.resolveCacheReference(element.getAttribute("cache-ref")); if (!subRegion) { builder.addPropertyReference("cache", resolvedCacheRef); @@ -89,9 +88,9 @@ abstract class AbstractRegionParser extends AbstractSingleBeanDefinitionParser { } ParsingUtils.parseOptionalRegionAttributes(parserContext, element, regionAttributesBuilder); + ParsingUtils.parseSubscription(parserContext, element, regionAttributesBuilder); ParsingUtils.parseStatistics(element, regionAttributesBuilder); ParsingUtils.parseExpiration(parserContext, element, regionAttributesBuilder); - ParsingUtils.parseSubscription(parserContext, element, regionAttributesBuilder); ParsingUtils.parseEviction(parserContext, element, regionAttributesBuilder); ParsingUtils.parseMembershipAttributes(parserContext, element, regionAttributesBuilder); @@ -146,18 +145,6 @@ abstract class AbstractRegionParser extends AbstractSingleBeanDefinitionParser { } } - private void parseSubRegions(Element element, ParserContext parserContext, String resolvedCacheRef) { - Map allSubRegionElements = new HashMap(); - - findSubRegionElements(element, getRegionNameFromElement(element), allSubRegionElements); - - if (!CollectionUtils.isEmpty(allSubRegionElements)) { - for (Map.Entry entry : allSubRegionElements.entrySet()) { - parseSubRegion(entry.getValue(), parserContext, entry.getKey(), resolvedCacheRef); - } - } - } - private void parseCollectionOfCustomSubElements(Element element, ParserContext parserContext, BeanDefinitionBuilder builder, String className, String subElementName, String propertyName) { List subElements = DomUtils.getChildElementsByTagName(element, @@ -174,6 +161,18 @@ abstract class AbstractRegionParser extends AbstractSingleBeanDefinitionParser { } } + protected void parseSubRegions(Element element, ParserContext parserContext, String resolvedCacheRef) { + Map allSubRegionElements = new HashMap(); + + findSubRegionElements(element, getRegionNameFromElement(element), allSubRegionElements); + + if (!CollectionUtils.isEmpty(allSubRegionElements)) { + for (Map.Entry entry : allSubRegionElements.entrySet()) { + parseSubRegion(entry.getValue(), parserContext, entry.getKey(), resolvedCacheRef); + } + } + } + private void findSubRegionElements(Element parent, String parentPath, Map allSubRegionElements) { for (Element element : DomUtils.getChildElements(parent)) { if (element.getLocalName().endsWith("region")) { 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 85d98596..67f9bab2 100644 --- a/src/main/java/org/springframework/data/gemfire/config/ClientRegionParser.java +++ b/src/main/java/org/springframework/data/gemfire/config/ClientRegionParser.java @@ -46,18 +46,19 @@ class ClientRegionParser extends AbstractRegionParser { } @Override - protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - super.doParse(element, builder); + protected void doParseRegion(Element element, ParserContext parserContext, BeanDefinitionBuilder builder, + boolean subRegion) { validateDataPolicyShortcutMutualExclusion(element, parserContext); - String cacheRefAttributeValue = element.getAttribute("cache-ref"); + String resolvedCacheRef = ParsingUtils.resolveCacheReference(element.getAttribute("cache-ref")); - builder.addPropertyReference("cache", (StringUtils.hasText(cacheRefAttributeValue) ? cacheRefAttributeValue - : GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME)); + if (!subRegion) { + builder.addPropertyReference("cache", resolvedCacheRef); + ParsingUtils.setPropertyValue(element, builder, "close"); + ParsingUtils.setPropertyValue(element, builder, "destroy"); + } - 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, "persistent"); @@ -72,8 +73,8 @@ class ClientRegionParser extends AbstractRegionParser { ParsingUtils.parseOptionalRegionAttributes(parserContext, element, regionAttributesBuilder); ParsingUtils.parseStatistics(element, regionAttributesBuilder); - ParsingUtils.parseEviction(parserContext, element, regionAttributesBuilder); ParsingUtils.parseExpiration(parserContext, element, regionAttributesBuilder); + ParsingUtils.parseEviction(parserContext, element, regionAttributesBuilder); builder.addPropertyValue("attributes", regionAttributesBuilder.getBeanDefinition()); @@ -107,14 +108,10 @@ class ClientRegionParser extends AbstractRegionParser { if (!interests.isEmpty()) { builder.addPropertyValue("interests", interests); } - } - @Override - protected void doParseRegion(Element element, ParserContext parserContext, BeanDefinitionBuilder builder, - boolean subRegion) { - throw new UnsupportedOperationException(String.format( - "doParseRegion(:Element, :ParserContext, :BeanDefinitionBuilder, :boolean) is not supported on %1$s", - getClass().getName())); + if (!subRegion) { + parseSubRegions(element, parserContext, resolvedCacheRef); + } } private void validateDataPolicyShortcutMutualExclusion(final Element element, final ParserContext parserContext) { @@ -134,6 +131,12 @@ class ClientRegionParser extends AbstractRegionParser { } } + 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"); + } + private Object parseKeyInterest(Element keyInterestElement, ParserContext parserContext) { BeanDefinitionBuilder keyInterestBuilder = BeanDefinitionBuilder.genericBeanDefinition(Interest.class); @@ -153,10 +156,4 @@ class ClientRegionParser extends AbstractRegionParser { return regexInterestBuilder.getBeanDefinition(); } - 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 1110ca42..29399fea 100644 --- a/src/main/java/org/springframework/data/gemfire/config/ParsingUtils.java +++ b/src/main/java/org/springframework/data/gemfire/config/ParsingUtils.java @@ -22,7 +22,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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.ParserContext; import org.springframework.core.Conventions; @@ -39,7 +38,7 @@ import com.gemstone.gemfire.cache.ResumptionAction; import com.gemstone.gemfire.cache.Scope; /** - * Various minor utilities used by the parser. + * Utilities used by the Spring Data GemFire XML Namespace parsers. *

* @author Costin Leau * @author David Turanski @@ -271,22 +270,25 @@ abstract class ParsingUtils { *

* @param parserContext the context used while parsing the XML document. * @param element the XML element being parsed. - * @param attrBuilder the Region Attributes builder. + * @param regionAttributesBuilder 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 = parseExpiration(element, "region-ttl", "regionTimeToLive", attrBuilder); + static boolean parseExpiration(ParserContext parserContext, Element element, + BeanDefinitionBuilder regionAttributesBuilder) { - 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); + boolean result = parseExpiration(element, "region-ttl", "regionTimeToLive", regionAttributesBuilder); + + result |= parseExpiration(element, "region-tti", "regionIdleTimeout", regionAttributesBuilder); + result |= parseExpiration(element, "entry-ttl", "entryTimeToLive", regionAttributesBuilder); + result |= parseExpiration(element, "entry-tti", "entryIdleTimeout", regionAttributesBuilder); + result |= parseCustomExpiration(parserContext, element,"custom-entry-ttl","customEntryTimeToLive", + regionAttributesBuilder); + result |= parseCustomExpiration(parserContext, element,"custom-entry-tti","customEntryIdleTimeout", + regionAttributesBuilder); - // TODO why? if (result) { // turn on statistics - attrBuilder.addPropertyValue("statisticsEnabled", Boolean.TRUE); + regionAttributesBuilder.addPropertyValue("statisticsEnabled", Boolean.TRUE); } return result; } @@ -314,7 +316,8 @@ abstract class ParsingUtils { String indexUpdateType = element.getAttribute("index-update-type"); if (StringUtils.hasText(indexUpdateType)) { - regionAttributesBuilder.addPropertyValue("indexMaintenanceSynchronous", "synchronous".equals(indexUpdateType)); + regionAttributesBuilder.addPropertyValue("indexMaintenanceSynchronous", + "synchronous".equals(indexUpdateType)); } String concurrencyChecksEnabled = element.getAttribute("concurrency-checks-enabled"); @@ -433,4 +436,9 @@ abstract class ParsingUtils { return true; } + + static String resolveCacheReference(final String cacheRef) { + return (StringUtils.hasText(cacheRef) ? cacheRef : GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME); + } + } diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.4.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.4.xsd index 585a07f0..b0512100 100644 --- a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.4.xsd +++ b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.4.xsd @@ -874,10 +874,11 @@ The name of the region definition. - - - - + + + + + @@ -965,7 +966,6 @@ Subscription policy for the replicated region. - - - + - + @@ -1130,7 +1129,7 @@ The name of the region definition. - + @@ -1348,8 +1347,7 @@ The name of the region definition. - - + @@ -1394,7 +1392,6 @@ When the region or cached object expires, it is destroyed locally only. Not supp - @@ -1416,7 +1413,6 @@ use inner bean declarations. - @@ -1491,7 +1487,7 @@ reclaim memory. - + @@ -1646,7 +1642,7 @@ The name of the bean defining the GemFire cache (by default 'gemfireCache'). - + - - - - - + + + + - + - - + + - + - - + + - - + + - - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/springframework/data/gemfire/client/ClientSubRegionTest.java b/src/test/java/org/springframework/data/gemfire/client/ClientSubRegionTest.java new file mode 100644 index 00000000..f321b6b3 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/client/ClientSubRegionTest.java @@ -0,0 +1,115 @@ +/* + * 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 static org.junit.Assert.assertSame; + +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.data.gemfire.ForkUtil; +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.data.gemfire.fork.SpringCacheServerProcess; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.client.ClientCache; + +/** + * The ClientSubRegionTest class is a test suite of test cases testing SubRegion functionality from a client + * GemFire Cache. + *

+ * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @since 1.4.0 + * @since 7.0.1 (GemFire) + */ +@ContextConfiguration("clientcache-with-subregion-config.xml") +@RunWith(SpringJUnit4ClassRunner.class) +@SuppressWarnings("unused") +public class ClientSubRegionTest { + + @Autowired + private ClientCache clientCache; + + @Resource(name = "parentTemplate") + private GemfireTemplate parentTemplate; + + @Resource(name = "childTemplate") + private GemfireTemplate childTemplate; + + @Resource(name = "Parent") + private Region parent; + + @Resource(name = "/Parent/Child") + private Region child; + + @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 testGemFireSubRegionCreationConfiguration() { + assertNotNull("The Client Cache was not properly initialized!", clientCache); + + Region parent = clientCache.getRegion("Parent"); + + assertNotNull(parent); + assertEquals("Parent", parent.getName()); + assertEquals("/Parent", parent.getFullPath()); + + Region child = parent.getSubregion("Child"); + + assertNotNull(child); + assertEquals("Child", child.getName()); + assertEquals("/Parent/Child", child.getFullPath()); + + Region clientCacheChild = clientCache.getRegion("/Parent/Child"); + + assertSame(child, clientCacheChild); + } + + @Test + public void testSpringSubRegionCreationConfiguration() { + assertNotNull(parent); + assertEquals("Parent", parent.getName()); + assertEquals("/Parent", parent.getFullPath()); + assertNotNull(child); + assertEquals("Child", child.getName()); + assertEquals("/Parent/Child", child.getFullPath()); + } + + @Test + public void testTemplateCreationConfiguration() { + assertNotNull(parentTemplate); + assertSame(parent, parentTemplate.getRegion()); + assertNotNull(childTemplate); + assertSame(child, childTemplate.getRegion()); + } + +} diff --git a/src/test/resources/org/springframework/data/gemfire/client/clientcache-with-subregion-config.xml b/src/test/resources/org/springframework/data/gemfire/client/clientcache-with-subregion-config.xml new file mode 100644 index 00000000..9d203761 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/client/clientcache-with-subregion-config.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + 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 index 3873c44d..d19f05ad 100644 --- 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 @@ -11,4 +11,8 @@ + + + +