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