diff --git a/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java index 7e45e124..afa903c2 100644 --- a/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java @@ -22,6 +22,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.core.io.Resource; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; @@ -32,6 +34,7 @@ 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; import com.gemstone.gemfire.cache.RegionAttributes; import com.gemstone.gemfire.cache.RegionFactory; @@ -41,6 +44,9 @@ import com.gemstone.gemfire.cache.Scope; * FactoryBean for creating generic GemFire {@link Region}s. Will try to first locate the region (by name) * and, in case none if found, proceed to creating one using the given settings. * + * Note that this factory bean allows for very flexible creation of GemFire {@link Region}. For "client" regions + * however, see {@link ClientRegionFactoryBean} which offers easier configuration and defaults. + * * @author Costin Leau */ public class RegionFactoryBean extends RegionLookupFactoryBean implements DisposableBean { @@ -68,12 +74,16 @@ public class RegionFactoryBean extends RegionLookupFactoryBean imple @Override - protected Region lookupFallback(Cache cache, String regionName) throws Exception { + protected Region lookupFallback(GemFireCache cache, String regionName) throws Exception { + Assert.isTrue(cache instanceof Cache, "Unable to create regions from " + cache); + + Cache c = (Cache) cache; + if (attributes != null) AttributesFactory.validateAttributes(attributes); - RegionFactory regionFactory = (attributes != null ? cache.createRegionFactory(attributes) - : cache. createRegionFactory()); + final RegionFactory regionFactory = (attributes != null ? c.createRegionFactory(attributes) + : c. createRegionFactory()); if (!ObjectUtils.isEmpty(cacheListeners)) { for (CacheListener listener : cacheListeners) { diff --git a/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java b/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java index 85a05c53..ec0a4db6 100644 --- a/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/RegionLookupFactoryBean.java @@ -25,7 +25,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.GemFireCache; import com.gemstone.gemfire.cache.Region; /** @@ -40,7 +40,7 @@ public class RegionLookupFactoryBean implements FactoryBean>, protected final Log log = LogFactory.getLog(getClass()); private String beanName; - private Cache cache; + private GemFireCache cache; private String name; Region region; @@ -68,7 +68,7 @@ public class RegionLookupFactoryBean implements FactoryBean>, * @param regionName region name * @throws Exception */ - protected Region lookupFallback(Cache cache, String regionName) throws Exception { + protected Region lookupFallback(GemFireCache cache, String regionName) throws Exception { throw new BeanInitializationException("Cannot find region [" + regionName + "] in cache " + cache); } @@ -94,7 +94,7 @@ public class RegionLookupFactoryBean implements FactoryBean>, * @see org.springframework.data.gemfire.CacheFactoryBean * @param cache the cache to set */ - public void setCache(Cache cache) { + public void setCache(GemFireCache cache) { this.cache = cache; } 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 5e4ec24c..2de597c0 100644 --- a/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java @@ -16,16 +16,27 @@ package org.springframework.data.gemfire.client; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.data.gemfire.RegionFactoryBean; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.core.io.Resource; +import org.springframework.data.gemfire.RegionLookupFactoryBean; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; -import com.gemstone.gemfire.cache.AttributesFactory; +import com.gemstone.gemfire.cache.CacheClosedException; +import com.gemstone.gemfire.cache.CacheListener; +import com.gemstone.gemfire.cache.DataPolicy; +import com.gemstone.gemfire.cache.GemFireCache; import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionAttributes; +import com.gemstone.gemfire.cache.client.ClientCache; +import com.gemstone.gemfire.cache.client.ClientRegionFactory; +import com.gemstone.gemfire.cache.client.ClientRegionShortcut; import com.gemstone.gemfire.cache.client.Pool; /** @@ -33,13 +44,110 @@ import com.gemstone.gemfire.cache.client.Pool; * * @author Costin Leau */ -public class ClientRegionFactoryBean extends RegionFactoryBean implements BeanFactoryAware { +public class ClientRegionFactoryBean extends RegionLookupFactoryBean implements BeanFactoryAware, + DisposableBean { + private static final Log log = LogFactory.getLog(ClientRegionFactoryBean.class); + + private boolean destroy = false; + private boolean close = true; + private Resource snapshot; + + private CacheListener cacheListeners[]; private Interest[] interests; private String poolName; private BeanFactory beanFactory; + private ClientRegionShortcut shortcut = null; + private DataPolicy dataPolicy; + + private RegionAttributes attributes; + + private Region region; + @Override + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + postProcess(region); + } + + @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; + + // first look at shortcut + ClientRegionShortcut s = null; + + 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; + } + } + s = ClientRegionShortcut.LOCAL; + } + + ClientRegionFactory factory = c.createClientRegionFactory(s); + + // map the attributes onto the client + if (attributes != null) { + CacheListener[] listeners = attributes.getCacheListeners(); + if (!ObjectUtils.isEmpty(cacheListeners)) { + for (CacheListener listener : cacheListeners) { + factory.addCacheListener(listener); + } + } + factory.setCloningEnabled(attributes.getCloningEnabled()); + factory.setConcurrencyLevel(attributes.getConcurrencyLevel()); + factory.setCustomEntryIdleTimeout(attributes.getCustomEntryIdleTimeout()); + factory.setCustomEntryTimeToLive(attributes.getCustomEntryTimeToLive()); + factory.setDiskStoreName(attributes.getDiskStoreName()); + factory.setDiskSynchronous(attributes.isDiskSynchronous()); + factory.setEntryIdleTimeout(attributes.getEntryIdleTimeout()); + factory.setEntryTimeToLive(attributes.getEntryTimeToLive()); + factory.setEvictionAttributes(attributes.getEvictionAttributes()); + factory.setInitialCapacity(attributes.getInitialCapacity()); + factory.setKeyConstraint(attributes.getKeyConstraint()); + factory.setLoadFactor(attributes.getLoadFactor()); + factory.setPoolName(attributes.getPoolName()); + factory.setRegionIdleTimeout(attributes.getRegionIdleTimeout()); + factory.setRegionTimeToLive(attributes.getRegionTimeToLive()); + factory.setStatisticsEnabled(attributes.getStatisticsEnabled()); + factory.setValueConstraint(attributes.getValueConstraint()); + } + + if (!ObjectUtils.isEmpty(cacheListeners)) { + for (CacheListener listener : cacheListeners) { + factory.addCacheListener(listener); + } + } + + if (StringUtils.hasText(poolName)) { + // try to eagerly initialize the pool name, if defined as a bean + if (beanFactory.isTypeMatch(poolName, Pool.class)) { + if (log.isDebugEnabled()) { + log.debug("Found bean definition for pool '" + poolName + "'. Eagerly initializing it..."); + } + beanFactory.getBean(poolName, Pool.class); + } + + factory.setPoolName(poolName); + } + + Region reg = factory.create(regionName); + log.info("Created new cache region [" + regionName + "]"); + if (snapshot != null) { + reg.loadSnapshot(snapshot.getInputStream()); + } + + return reg; + } + protected void postProcess(Region region) { if (!ObjectUtils.isEmpty(interests)) { for (Interest interest : interests) { @@ -54,22 +162,6 @@ public class ClientRegionFactoryBean extends RegionFactoryBean imple } } - @Override - protected void postProcess(AttributesFactory attrFactory) { - if (StringUtils.hasText(poolName)) { - // try to eagerly initialize the pool name, if defined as a bean - if (beanFactory.isTypeMatch(poolName, Pool.class)) { - if (log.isDebugEnabled()) { - log.debug("Found bean definition for pool '" + poolName + "'. Eagerly initializing it..."); - } - beanFactory.getBean(poolName, Pool.class); - } - - attrFactory.setPoolName(poolName); - } - } - - @Override public void destroy() throws Exception { Region region = getObject(); // unregister interests @@ -89,7 +181,21 @@ public class ClientRegionFactoryBean extends RegionFactoryBean imple log.warn("Cannot unregister cache interests", ex); } - super.destroy(); + if (region != null) { + if (close) { + if (!region.getCache().isClosed()) { + try { + region.close(); + } catch (CacheClosedException cce) { + // nothing to see folks, move on. + } + } + } + else if (destroy) { + region.destroyRegion(); + } + } + region = null; } public void setBeanFactory(BeanFactory beanFactory) throws BeansException { @@ -132,4 +238,92 @@ public class ClientRegionFactoryBean extends RegionFactoryBean imple Assert.notNull(pool, "pool cannot be null"); setPoolName(pool.getName()); } + + /** + * Initializes the client using a GemFire {@link ClientRegionShortcut}. + * The recommended way for creating clients since it covers all the major scenarios with minimal + * configuration. + * + * @param shortcut + */ + public void setShortcut(ClientRegionShortcut shortcut) { + this.shortcut = shortcut; + } + + /** + * 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; + + if (destroy) { + close = false; + } + } + + /** + * 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; + } + } + + /** + * Sets the snapshots used for loading a newly created region. + * That is, the snapshot will be used only when a new region is created - if the region + * already exists, no loading will be performed. + * + * @see #setName(String) + * @param snapshot the snapshot to set + */ + public void setSnapshot(Resource snapshot) { + this.snapshot = snapshot; + } + + /** + * Sets the cache listeners used for the region used by this factory. + * Used only when a new region is created.Overrides the settings + * specified through {@link #setAttributes(RegionAttributes)}. + * + * @param cacheListeners the cacheListeners to set on a newly created region + */ + public void setCacheListeners(CacheListener[] cacheListeners) { + this.cacheListeners = cacheListeners; + } + + /** + * Sets the data policy. Used only when a new region is created. + * + * @param dataPolicy the region data policy + */ + public void setDataPolicy(DataPolicy dataPolicy) { + this.dataPolicy = dataPolicy; + } + + /** + * Sets the region attributes used for the region used by this factory. + * Allows maximum control in specifying the region settings. + * Used only when a new region is created. + * Note that using this method allows for advanced customization of the region - while it provides a lot of flexibility, + * note that it's quite easy to create misconfigured regions (especially in a client/server scenario). + * + * @param attributes the attributes to set on a newly created region + */ + 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 22affcea..bc817d79 100644 --- a/src/main/java/org/springframework/data/gemfire/config/ClientRegionParser.java +++ b/src/main/java/org/springframework/data/gemfire/config/ClientRegionParser.java @@ -17,7 +17,6 @@ package org.springframework.data.gemfire.config; import java.util.List; -import java.util.concurrent.ConcurrentMap; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedList; @@ -30,10 +29,7 @@ import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; -import com.gemstone.gemfire.cache.CacheFactory; import com.gemstone.gemfire.cache.DataPolicy; -import com.gemstone.gemfire.cache.Region; -import com.gemstone.gemfire.cache.Scope; /** * Parser for <client-region;gt; definitions. @@ -57,11 +53,11 @@ class ClientRegionParser extends AliasReplacingBeanDefinitionParser { // 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 - builder.addPropertyValue("scope", Scope.LOCAL); - ParsingUtils.setPropertyValue(element, builder, "data-policy", "dataPolicy"); ParsingUtils.setPropertyValue(element, builder, "name", "name"); ParsingUtils.setPropertyValue(element, builder, "pool-name", "poolName"); + ParsingUtils.setPropertyValue(element, builder, "shortcut", "shortcut"); + // set the persistent policy String attr = element.getAttribute("persistent"); @@ -70,15 +66,8 @@ class ClientRegionParser extends AliasReplacingBeanDefinitionParser { if (Boolean.parseBoolean(attr)) { // check first for GemFire 6.5 - if (ConcurrentMap.class.isAssignableFrom(Region.class)) { - builder.addPropertyValue("dataPolicy", DataPolicy.PERSISTENT_REPLICATE); - frozenDataPolicy = true; - } - else { - parserContext.getReaderContext().error( - "Can define persistent partitions only from GemFire 6.5 onwards - current version is [" - + CacheFactory.getVersion() + "]", element); - } + builder.addPropertyValue("dataPolicy", DataPolicy.PERSISTENT_REPLICATE); + frozenDataPolicy = true; } attr = element.getAttribute("cache-ref"); diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.1.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.1.xsd index 45f183e7..47039d54 100644 --- a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.1.xsd +++ b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.1.xsd @@ -840,6 +840,26 @@ The name of the pool used by this client. If not set, a default pool (initialize ]]> + + + + + + + + + + + + + + + + + + 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 2b8ca351..e7e4ae7e 100644 --- a/src/test/java/org/springframework/data/gemfire/config/ClientRegionNamespaceTest.java +++ b/src/test/java/org/springframework/data/gemfire/config/ClientRegionNamespaceTest.java @@ -43,7 +43,6 @@ import com.gemstone.gemfire.cache.EvictionAlgorithm; import com.gemstone.gemfire.cache.EvictionAttributes; import com.gemstone.gemfire.cache.InterestResultPolicy; import com.gemstone.gemfire.cache.RegionAttributes; -import com.gemstone.gemfire.cache.Scope; import com.gemstone.gemfire.cache.util.ObjectSizer; /** @@ -71,7 +70,6 @@ public class ClientRegionNamespaceTest { assertTrue(context.containsBean("empty")); ClientRegionFactoryBean fb = context.getBean("&empty", ClientRegionFactoryBean.class); assertEquals(DataPolicy.EMPTY, TestUtils.readField("dataPolicy", fb)); - assertEquals(Scope.LOCAL, TestUtils.readField("scope", fb)); } //@Test @@ -103,7 +101,6 @@ public class ClientRegionNamespaceTest { assertTrue(context.containsBean("persistent")); ClientRegionFactoryBean fb = context.getBean("&persistent", ClientRegionFactoryBean.class); assertEquals(DataPolicy.PERSISTENT_REPLICATE, TestUtils.readField("dataPolicy", fb)); - assertEquals(Scope.LOCAL, TestUtils.readField("scope", fb)); RegionAttributes attrs = TestUtils.readField("attributes", fb); File[] diskDirs = attrs.getDiskDirs(); assertEquals(1, diskDirs.length);