From b42ca233f05a8bfe44204d7111721f5750da9f7c Mon Sep 17 00:00:00 2001 From: John Blum Date: Tue, 7 Apr 2015 19:27:15 -0700 Subject: [PATCH] SGF-196 - Support adding CacheListeners, CacheLoaders and CacheWriters, along with other mutable Region attributes to an existing Region. --- .../data/gemfire/LookupRegionFactoryBean.java | 206 +++++++++++++++- .../data/gemfire/RegionLookupFactoryBean.java | 44 ++-- .../gemfire/config/AbstractRegionParser.java | 30 ++- .../gemfire/config/LookupRegionParser.java | 42 +++- .../gemfire/config/spring-gemfire-1.7.xsd | 172 ++++++++++++- .../gemfire/LookupRegionFactoryBeanTest.java | 209 ++++++++++++++++ .../LookupRegionMutationIntegrationTest.java | 230 ++++++++++++++++++ .../lookup-region-mutation-cache.xml | 13 + ...pRegionMutationIntegrationTest-context.xml | 48 ++++ 9 files changed, 959 insertions(+), 35 deletions(-) create mode 100644 src/test/java/org/springframework/data/gemfire/LookupRegionFactoryBeanTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/LookupRegionMutationIntegrationTest.java create mode 100644 src/test/resources/lookup-region-mutation-cache.xml create mode 100644 src/test/resources/org/springframework/data/gemfire/LookupRegionMutationIntegrationTest-context.xml diff --git a/src/main/java/org/springframework/data/gemfire/LookupRegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/LookupRegionFactoryBean.java index 9d4e091b..2a57dc35 100644 --- a/src/main/java/org/springframework/data/gemfire/LookupRegionFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/LookupRegionFactoryBean.java @@ -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/< 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 extends RegionLookupFactoryBean { + private Boolean cloningEnabled; + private Boolean enableStatistics; + + private AsyncEventQueue[] asyncEventQueues; + + private CacheListener[] cacheListeners; + + private CacheLoader cacheLoader; + + private CacheWriter cacheWriter; + + private CustomExpiry customEntryIdleTimeout; + private CustomExpiry 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 attributesMutator = getRegion().getAttributesMutator(); + + if (!ObjectUtils.isEmpty(asyncEventQueues)) { + for (AsyncEventQueue asyncEventQueue : asyncEventQueues) { + attributesMutator.addAsyncEventQueueId(asyncEventQueue.getId()); + } + } + + if (!ObjectUtils.isEmpty(cacheListeners)) { + for (CacheListener 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[] cacheListeners) { + this.cacheListeners = cacheListeners; + } + + /* (non-Javadoc) */ + public void setCacheLoader(CacheLoader cacheLoader) { + this.cacheLoader = cacheLoader; + } + + /* (non-Javadoc) */ + public void setCacheWriter(CacheWriter cacheWriter) { + this.cacheWriter = cacheWriter; + } + + /* (non-Javadoc) */ + public void setCloningEnabled(Boolean cloningEnabled) { + this.cloningEnabled = cloningEnabled; + } + + /* (non-Javadoc) */ + public void setCustomEntryIdleTimeout(CustomExpiry customEntryIdleTimeout) { + setStatisticsEnabled(customEntryIdleTimeout != null); + this.customEntryIdleTimeout = customEntryIdleTimeout; + } + + /* (non-Javadoc) */ + public void setCustomEntryTimeToLive(CustomExpiry 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())); + } + } diff --git a/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java b/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java index 55ef5dc7..859df298 100644 --- a/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java @@ -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 implements FactoryBean>, InitializingBean, BeanNameAware { @@ -45,19 +49,19 @@ public abstract class RegionLookupFactoryBean implements FactoryBean parent; - private Region region; + private volatile Region 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 implements FactoryBean implements FactoryBean 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 implements FactoryBean 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 implements FactoryBean getRegion() { - return region; + /* (non-Javadoc) */ + public Boolean getLookupEnabled() { + return lookupEnabled; } } 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 023a37ff..ab343643 100644 --- a/src/main/java/org/springframework/data/gemfire/config/AbstractRegionParser.java +++ b/src/main/java/org/springframework/data/gemfire/config/AbstractRegionParser.java @@ -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 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 subElements = DomUtils.getChildElementsByTagName(element, subElementName, subElementName + "-ref"); diff --git a/src/main/java/org/springframework/data/gemfire/config/LookupRegionParser.java b/src/main/java/org/springframework/data/gemfire/config/LookupRegionParser.java index db901e28..299788aa 100644 --- a/src/main/java/org/springframework/data/gemfire/config/LookupRegionParser.java +++ b/src/main/java/org/springframework/data/gemfire/config/LookupRegionParser.java @@ -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 <lookup-region;gt; definitions. + * Parser for GFE <lookup-region> 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); diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.7.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.7.xsd index 2774af09..26bc15d5 100644 --- a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.7.xsd +++ b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.7.xsd @@ -448,7 +448,174 @@ Defines a lookup Subregion - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -589,8 +756,7 @@ CustomExpiry Time-to-Live (TTL) configuration for the Region Entries. The defaul - + mockRegion = mock(Region.class, "testAfterPropertiesSet.MockRegion"); + + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class, + "testAfterPropertiesSet.MockRegionAttributes"); + + EvictionAttributesMutator mockEvictionAttributesMutator = mock(EvictionAttributesMutator.class, + "testAfterPropertiesSet.EvictionAttributesMutator"); + + AttributesMutator 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 mockRegion = mock(Region.class, "testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified.MockRegion"); + + RegionAttributes 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)); + } + } + +} diff --git a/src/test/java/org/springframework/data/gemfire/LookupRegionMutationIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/LookupRegionMutationIntegrationTest.java new file mode 100644 index 00000000..487d00a1 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/LookupRegionMutationIntegrationTest.java @@ -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 expectedCacheListenerNames) { + if (!expectedCacheListenerNames.isEmpty()) { + assertNotNull("CacheListeners must not be null!", cacheListeners); + assertEquals(expectedCacheListenerNames.size(), cacheListeners.length); + assertTrue(toStrings(cacheListeners).containsAll(expectedCacheListenerNames)); + } + } + + protected Collection toStrings(Object[] objects) { + List cacheListenerNames = new ArrayList(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 events) { + throw new UnsupportedOperationException("Not Implemented!"); + } + + @Override public void close() { } + } + + public static final class TestCacheListener extends CacheListenerAdapter 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 extends AbstractNameable implements CacheLoader { + + @Override + public V load(LoaderHelper helper) throws CacheLoaderException { + throw new UnsupportedOperationException("Not Implemented!"); + } + + @Override + public void close() { } + } + + public static final class TestCacheWriter extends AbstractNameable implements CacheWriter { + + @Override public void beforeUpdate(EntryEvent event) throws CacheWriterException { } + + @Override public void beforeCreate(EntryEvent event) throws CacheWriterException { } + + @Override public void beforeDestroy(EntryEvent event) throws CacheWriterException { } + + @Override public void beforeRegionDestroy(RegionEvent event) throws CacheWriterException { } + + @Override public void beforeRegionClear(RegionEvent event) throws CacheWriterException { } + + @Override public void close() { } + } + + public static final class TestCustomExpiry extends AbstractNameable implements CustomExpiry { + + @Override public ExpirationAttributes getExpiry(Region.Entry entry) { + throw new UnsupportedOperationException("Not Implemented!"); + } + + @Override public void close() { } + } + +} diff --git a/src/test/resources/lookup-region-mutation-cache.xml b/src/test/resources/lookup-region-mutation-cache.xml new file mode 100644 index 00000000..f5f18b63 --- /dev/null +++ b/src/test/resources/lookup-region-mutation-cache.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/LookupRegionMutationIntegrationTest-context.xml b/src/test/resources/org/springframework/data/gemfire/LookupRegionMutationIntegrationTest-context.xml new file mode 100644 index 00000000..5a7076d7 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/LookupRegionMutationIntegrationTest-context.xml @@ -0,0 +1,48 @@ + + + + + LookupRegionMutationIntegrationTest + 0 + warning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +