diff --git a/gradle.properties b/gradle.properties
index 30f139b7..85299469 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,7 +1,7 @@
antlrVersion=2.7.7
aspectjVersion=1.7.4
gemfire.range="[6.5, 8.0)"
-gemfireVersion=7.5.BUILD-SNAPSHOT
+gemfireVersion=8.0.BUILD-SNAPSHOT
hamcrestVersion=1.3
jacksonVersion=2.4.1
junitVersion=4.11
diff --git a/src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java b/src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java
index a3f05f39..c80ea232 100644
--- a/src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java
+++ b/src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java
@@ -58,24 +58,28 @@ import com.gemstone.gemfire.pdx.PdxSerializable;
import com.gemstone.gemfire.pdx.PdxSerializer;
/**
- * Factory used for configuring a Gemfire Cache manager. Allows either retrieval
- * of an existing, opened cache or the creation of a new one.
- *
+ * FactoryBean used to configure a GemFire peer Cache node. Allows either retrieval of an existing, opened Cache
+ * or the creation of a new Cache instance.
*
- * This class implements the
- * {@link org.springframework.dao.support.PersistenceExceptionTranslator}
+ * This class implements the {@link org.springframework.dao.support.PersistenceExceptionTranslator}
* interface, as auto-detected by Spring's
- * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor}
- * , for AOP-based translation of native exceptions to Spring
- * DataAccessExceptions. Hence, the presence of this class automatically enables
- * a PersistenceExceptionTranslationPostProcessor to translate GemFire
- * exceptions.
+ * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor}, for AOP-based translation
+ * of native Exceptions to Spring DataAccessExceptions. Hence, the presence of this class automatically enables
+ * a PersistenceExceptionTranslationPostProcessor to translate GemFire Exceptions appropriately.
*
* @author Costin Leau
* @author David Turanski
+ * @author John Blum
+ * @see org.springframework.beans.factory.BeanClassLoaderAware
+ * @see org.springframework.beans.factory.BeanFactoryAware
+ * @see org.springframework.beans.factory.BeanNameAware
+ * @see org.springframework.beans.factory.FactoryBean
+ * @see org.springframework.beans.factory.InitializingBean
+ * @see org.springframework.beans.factory.DisposableBean
+ * @see org.springframework.dao.support.PersistenceExceptionTranslator
*/
-public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanClassLoaderAware, InitializingBean,
- DisposableBean, FactoryBean, PersistenceExceptionTranslator {
+public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, BeanNameAware, FactoryBean,
+ InitializingBean, DisposableBean, PersistenceExceptionTranslator {
protected static final List VALID_JNDI_DATASOURCE_TYPE_NAMES = Collections.unmodifiableList(
Arrays.asList("ManagedDataSource", "PooledDataSource", "SimpleDataSource", "XAPooledDataSource"));
@@ -93,6 +97,7 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
protected Boolean pdxIgnoreUnreadFields;
protected Boolean pdxPersistent;
protected Boolean pdxReadSerialized;
+ protected Boolean useSharedConfiguration;
protected Cache cache;
@@ -114,7 +119,7 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
protected List transactionListeners;
- // Defined this way for backward compatibility
+ // Declared with type 'Object' for backward compatibility
protected Object gatewayConflictResolver;
protected Object pdxSerializer;
@@ -128,20 +133,19 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
protected TransactionWriter transactionWriter;
public static class DynamicRegionSupport {
- private String diskDir;
-
- private String poolName;
private Boolean persistent = Boolean.TRUE;
-
private Boolean registerInterest = Boolean.TRUE;
+ private String diskDirectory;
+ private String poolName;
+
public String getDiskDir() {
- return diskDir;
+ return diskDirectory;
}
- public void setDiskDir(String diskDir) {
- this.diskDir = diskDir;
+ public void setDiskDir(String diskDirectory) {
+ this.diskDirectory = diskDirectory;
}
public Boolean getPersistent() {
@@ -152,14 +156,6 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
this.persistent = persistent;
}
- public Boolean getRegisterInterest() {
- return registerInterest;
- }
-
- public void setRegisterInterest(Boolean registerInterest) {
- this.registerInterest = registerInterest;
- }
-
public String getPoolName() {
return poolName;
}
@@ -168,13 +164,20 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
this.poolName = poolName;
}
+ public Boolean getRegisterInterest() {
+ return registerInterest;
+ }
+
+ public void setRegisterInterest(Boolean registerInterest) {
+ this.registerInterest = registerInterest;
+ }
+
public void initializeDynamicRegionFactory() {
- DynamicRegionFactory.Config config = null;
- if (diskDir == null) {
- config = new DynamicRegionFactory.Config(null, poolName, persistent, registerInterest);
- } else {
- config = new DynamicRegionFactory.Config(new File(diskDir), poolName, persistent, registerInterest);
- }
+ File localDiskDirectory = (this.diskDirectory == null ? null : new File(this.diskDirectory));
+
+ DynamicRegionFactory.Config config = new DynamicRegionFactory.Config(localDiskDirectory, poolName,
+ persistent, registerInterest);
+
DynamicRegionFactory.get().open(config);
}
}
@@ -182,7 +185,6 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
public static class JndiDataSource {
private List props;
-
private Map attributes;
public Map getAttributes() {
@@ -294,8 +296,8 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
DistributedMember member = system.getDistributedMember();
- log.info(String.format("Connected to Distributed System [%1$s] as Member [%2$s] on Host [%3$s].",
- system.getName(), member.getId(), member.getHost()));
+ log.info(String.format("Connected to Distributed System [%1$s] as Member [%2$s] in Groups [%3$s] with Roles [%4$s] on Host [%5$s] having PID [%6$d].",
+ system.getName(), member.getId(), member.getGroups(), member.getRoles(), member.getHost(), member.getProcessId()));
log.info(String.format("%1$s GemFire v.%2$s Cache [%3$s].", messagePrefix, CacheFactory.getVersion(),
cache.getName()));
@@ -428,43 +430,30 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
}
@Override
- public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
- if (ex instanceof GemFireException) {
- return GemfireCacheUtils.convertGemfireAccessException((GemFireException) ex);
+ public DataAccessException translateExceptionIfPossible(final RuntimeException e) {
+ if (e instanceof GemFireException) {
+ return GemfireCacheUtils.convertGemfireAccessException((GemFireException) e);
}
- if (ex instanceof IllegalArgumentException) {
- DataAccessException wrapped = GemfireCacheUtils.convertQueryExceptions(ex);
+
+ if (e instanceof IllegalArgumentException) {
+ DataAccessException wrapped = GemfireCacheUtils.convertQueryExceptions(e);
// ignore conversion if the generic exception is returned
if (!(wrapped instanceof GemfireSystemException)) {
return wrapped;
}
}
- if (ex.getCause() instanceof GemFireException) {
- return GemfireCacheUtils.convertGemfireAccessException((GemFireException) ex.getCause());
+
+ if (e.getCause() instanceof GemFireException) {
+ return GemfireCacheUtils.convertGemfireAccessException((GemFireException) e.getCause());
}
- if (ex.getCause() instanceof GemFireCheckedException) {
- return GemfireCacheUtils.convertGemfireAccessException((GemFireCheckedException) ex.getCause());
+
+ if (e.getCause() instanceof GemFireCheckedException) {
+ return GemfireCacheUtils.convertGemfireAccessException((GemFireCheckedException) e.getCause());
}
return null;
}
- @Override
- public Cache getObject() throws Exception {
- init();
- return cache;
- }
-
- @Override
- public Class extends Cache> getObjectType() {
- return (cache != null ? cache.getClass() : Cache.class);
- }
-
- @Override
- public boolean isSingleton() {
- return true;
- }
-
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
@@ -518,7 +507,13 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
this.useBeanFactoryLocator = usage;
}
- public void setEnableAutoReconnect(final Boolean enableAutoReconnect) {
+ /**
+ * Controls whether auto-reconnect functionality introduced in GemFire 8 is enabled or not.
+ *
+ * @param enableAutoReconnect a boolean value to enable/disable auto-reconnect functionality.
+ * @since GemFire 8.0
+ */
+ public void setEnableAutoReconnect(Boolean enableAutoReconnect) {
this.enableAutoReconnect = enableAutoReconnect;
}
@@ -534,25 +529,24 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
}
/**
- * Sets the object preference to PdxInstance type. Applicable on GemFire 6.6
- * or higher.
- *
- * @param pdxPersistent the pdxPersistent to set
- */
- public void setPdxPersistent(Boolean pdxPersistent) {
- this.pdxPersistent = pdxPersistent;
- }
-
- /**
- * Controls whether the type metadata for PDX objects is persisted to disk.
- * Applicable on GemFire 6.6 or higher.
- *
- * @param pdxReadSerialized the pdxReadSerialized to set
+ * Sets the object preference to PdxInstance. Applicable on GemFire 6.6 or higher.
+ *
+ * @param pdxReadSerialized a boolean value indicating the PDX instance should be returned from Region.get(key)
+ * when available.
*/
public void setPdxReadSerialized(Boolean pdxReadSerialized) {
this.pdxReadSerialized = pdxReadSerialized;
}
+ /**
+ * Controls whether type metadata for PDX objects is persisted to disk. Applicable on GemFire 6.6 or higher.
+ *
+ * @param pdxPersistent a boolean value indicating that PDX type meta-data should be persisted to disk.
+ */
+ public void setPdxPersistent(Boolean pdxPersistent) {
+ this.pdxPersistent = pdxPersistent;
+ }
+
/**
* Controls whether pdx ignores fields that were unread during
* deserialization. Applicable on GemFire 6.6 or higher.
@@ -574,10 +568,12 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
}
/**
- * @return the beanFactory
+ * Set whether the Cache should be closed.
+ *
+ * @param close set to false if destroy() should not close the cache
*/
- public BeanFactory getBeanFactory() {
- return beanFactory;
+ public void setClose(boolean close) {
+ this.close = close;
}
/**
@@ -590,12 +586,21 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
}
/**
- * Sets the number of seconds in which the implicit object lock request will timeout.
+ * Set the Cache's critical heap percentage attribute.
*
- * @param lockTimeout an integer value specifying the object lock request timeout.
+ * @param criticalHeapPercentage floating point value indicating the critical heap percentage.
*/
- public void setLockTimeout(Integer lockTimeout) {
- this.lockTimeout = lockTimeout;
+ public void setCriticalHeapPercentage(Float criticalHeapPercentage) {
+ this.criticalHeapPercentage = criticalHeapPercentage;
+ }
+
+ /**
+ * Set the Cache's eviction heap percentage attribute.
+ *
+ * @param evictionHeapPercentage float-point value indicating the Cache's heap use percentage to trigger eviction.
+ */
+ public void setEvictionHeapPercentage(Float evictionHeapPercentage) {
+ this.evictionHeapPercentage = evictionHeapPercentage;
}
/**
@@ -607,6 +612,15 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
this.lockLease = lockLease;
}
+ /**
+ * Sets the number of seconds in which the implicit object lock request will timeout.
+ *
+ * @param lockTimeout an integer value specifying the object lock request timeout.
+ */
+ public void setLockTimeout(Integer lockTimeout) {
+ this.lockTimeout = lockTimeout;
+ }
+
/**
* Set for client subscription queue synchronization when this member acts as a server to clients
* and server redundancy is used. Sets the frequency (in seconds) at which the primary server sends messages
@@ -628,33 +642,6 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
this.searchTimeout = searchTimeout;
}
- /**
- * Set the Cache's eviction heap percentage attribute.
- *
- * @param evictionHeapPercentage float-point value indicating the Cache's heap use percentage to trigger eviction.
- */
- public void setEvictionHeapPercentage(Float evictionHeapPercentage) {
- this.evictionHeapPercentage = evictionHeapPercentage;
- }
-
- /**
- * Set the Cache's critical heap percentage attribute.
- *
- * @param criticalHeapPercentage floating point value indicating the critical heap percentage.
- */
- public void setCriticalHeapPercentage(Float criticalHeapPercentage) {
- this.criticalHeapPercentage = criticalHeapPercentage;
- }
-
- /**
- * Set whether the Cache should be closed.
- *
- * @param close set to false if destroy() should not close the cache
- */
- public void setClose(boolean close) {
- this.close = close;
- }
-
/**
* Sets the list of TransactionListeners used to configure the Cache to receive transaction events after
* the transaction is processed (committed, rolled back).
@@ -677,16 +664,6 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
this.transactionWriter = transactionWriter;
}
- /**
- * Requires GemFire 7.0 or higher
- * @param gatewayConflictResolver defined as Object in the signature for backward
- * compatibility with Gemfire 6 compatibility. This must be an instance of
- * {@link com.gemstone.gemfire.cache.util.GatewayConflictResolver}
- */
- public void setGatewayConflictResolver(Object gatewayConflictResolver) {
- this.gatewayConflictResolver = gatewayConflictResolver;
- }
-
/**
* Sets an instance of the DynamicRegionSupport to support Dynamic Regions in this GemFire Cache.
*
@@ -696,6 +673,16 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
this.dynamicRegionSupport = dynamicRegionSupport;
}
+ /**
+ * Requires GemFire 7.0 or higher
+ * @param gatewayConflictResolver defined as Object in the signature for backward
+ * compatibility with Gemfire 6 compatibility. This must be an instance of
+ * {@link com.gemstone.gemfire.cache.util.GatewayConflictResolver}
+ */
+ public void setGatewayConflictResolver(Object gatewayConflictResolver) {
+ this.gatewayConflictResolver = gatewayConflictResolver;
+ }
+
/**
* @param jndiDataSources the list of configured JndiDataSources to use with this Cache.
*/
@@ -704,10 +691,12 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
}
/**
- * @return the list of configured JndiDataSources.
+ * Sets the state of the use-shared-configuration GemFire distribution config setting.
+ *
+ * @param useSharedConfiguration a boolean value to set the use-shared-configuration GemFire distribution property.
*/
- public List getJndiDataSources() {
- return jndiDataSources;
+ public void setUseSharedConfiguration(Boolean useSharedConfiguration) {
+ this.useSharedConfiguration = useSharedConfiguration;
}
/**
@@ -717,6 +706,20 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
return beanClassLoader;
}
+ /**
+ * @return the beanFactory
+ */
+ public BeanFactory getBeanFactory() {
+ return beanFactory;
+ }
+
+ /**
+ * @return the beanFactoryLocator
+ */
+ public GemfireBeanFactoryLocator getBeanFactoryLocator() {
+ return factoryLocator;
+ }
+
/**
* @return the beanName
*/
@@ -742,10 +745,53 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
return properties;
}
+ @Override
+ public Cache getObject() throws Exception {
+ init();
+ return cache;
+ }
+
+ @Override
+ public Class extends Cache> getObjectType() {
+ return (cache != null ? cache.getClass() : Cache.class);
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return true;
+ }
+
+ /**
+ * @return the copyOnRead
+ */
+ public Boolean getCopyOnRead() {
+ return copyOnRead;
+ }
+
+ /**
+ * @return the criticalHeapPercentage
+ */
+ public Float getCriticalHeapPercentage() {
+ return criticalHeapPercentage;
+ }
+
+ /**
+ * Gets the value for the auto-reconnect setting.
+ *
+ * @return a boolean value indicating whether auto-reconnect was specified (non-null) and whether it was enabled
+ * or not.
+ */
public Boolean getEnableAutoReconnect() {
return enableAutoReconnect;
}
+ /**
+ * @return the evictionHeapPercentage
+ */
+ public Float getEvictionHeapPercentage() {
+ return evictionHeapPercentage;
+ }
+
/**
* @return the pdxSerializer
*/
@@ -753,13 +799,6 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
return pdxSerializer;
}
- /**
- * @return the pdxPersistent
- */
- public Boolean getPdxPersistent() {
- return pdxPersistent;
- }
-
/**
* @return the pdxReadSerialized
*/
@@ -767,6 +806,13 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
return pdxReadSerialized;
}
+ /**
+ * @return the pdxPersistent
+ */
+ public Boolean getPdxPersistent() {
+ return pdxPersistent;
+ }
+
/**
* @return the pdxIgnoreUnreadFields
*/
@@ -782,10 +828,10 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
}
/**
- * @return the copyOnRead
+ * @return the lockLease
*/
- public Boolean getCopyOnRead() {
- return copyOnRead;
+ public Integer getLockLease() {
+ return lockLease;
}
/**
@@ -795,13 +841,6 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
return lockTimeout;
}
- /**
- * @return the lockLease
- */
- public Integer getLockLease() {
- return lockLease;
- }
-
/**
* @return the messageSyncInterval
*/
@@ -830,21 +869,6 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
return transactionWriter;
}
- /**
- * @return the evictionHeapPercentage
- */
- public Float getEvictionHeapPercentage() {
- return evictionHeapPercentage;
- }
-
- /**
- * @return the criticalHeapPercentage
- */
- public Float getCriticalHeapPercentage() {
-
- return criticalHeapPercentage;
- }
-
/**
* @return the dynamicRegionSupport
*/
@@ -860,10 +884,19 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
}
/**
- * @return the beanFactoryLocator
+ * @return the list of configured JndiDataSources.
*/
- public GemfireBeanFactoryLocator getBeanFactoryLocator() {
- return factoryLocator;
+ public List getJndiDataSources() {
+ return jndiDataSources;
+ }
+
+ /**
+ * Gets the value fo the use-shared-configuration GemFire setting.
+ *
+ * @return a boolean value indicating whether shared configuration use has been enabled or not.
+ */
+ public Boolean getUseSharedConfiguration() {
+ return this.useSharedConfiguration;
}
/**
@@ -878,6 +911,8 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
protected void postProcessPropertiesBeforeInitialization(Properties gemfireProperties) {
gemfireProperties.setProperty("disable-auto-reconnect", String.valueOf(
!Boolean.TRUE.equals(getEnableAutoReconnect())));
+ gemfireProperties.setProperty("use-shared-configuration", String.valueOf(
+ Boolean.TRUE.equals(getUseSharedConfiguration())));
}
/* (non-Javadoc)
@@ -891,4 +926,5 @@ public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanCl
init();
}
}
+
}
diff --git a/src/main/java/org/springframework/data/gemfire/client/ClientCacheFactoryBean.java b/src/main/java/org/springframework/data/gemfire/client/ClientCacheFactoryBean.java
index 4d7d4466..159580d3 100644
--- a/src/main/java/org/springframework/data/gemfire/client/ClientCacheFactoryBean.java
+++ b/src/main/java/org/springframework/data/gemfire/client/ClientCacheFactoryBean.java
@@ -89,14 +89,12 @@ public class ClientCacheFactoryBean extends CacheFactoryBean {
ClientCacheFactory clientCacheFactory = (ClientCacheFactory) factory;
initializePool(clientCacheFactory);
-
- // Now create the cache
+
GemFireCache cache = clientCacheFactory.create();
-
- // Register for events after pool/regions been created and iff non-durable client
+
+ // register for events after Pool and Regions been created and iff non-durable client...
readyForEvents();
-
- // Return the cache
+
return cache;
}
@@ -110,86 +108,110 @@ public class ClientCacheFactoryBean extends CacheFactoryBean {
return ClientCacheFactory.getAnyInstance();
}
- private void initializePool(ClientCacheFactory ccf) {
- Pool p = pool;
+ private void initializePool(ClientCacheFactory clientCacheFactory) {
+ Pool localPool = pool;
- if (p == null) {
+ if (localPool == null) {
if (StringUtils.hasText(poolName)) {
- p = PoolManager.find(poolName);
+ localPool = PoolManager.find(poolName);
}
// Bind this client cache to a pool that hasn't been created yet.
- if (p == null) {
+ if (localPool == null) {
PoolFactoryBean.connectToTemporaryDs(this.properties);
}
if (StringUtils.hasText(poolName)) {
try {
-
getBeanFactory().isTypeMatch(poolName, Pool.class);
- } catch (Exception e) {
- String msg = "No bean found with name " + poolName
- + " of type " + Pool.class.getName();
- if (poolName
- .equals(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME)) {
- msg += ". A client cache requires a pool";
- }
- throw new BeanInitializationException(msg);
+ localPool = getBeanFactory().getBean(poolName, Pool.class);
}
- p = getBeanFactory().getBean(poolName, Pool.class);
- } else {
+ catch (Exception e) {
+ String message = String.format("No bean found with name '%1$s' of type '%2$s'.%3$s", poolName,
+ Pool.class.getName(), (GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME.equals(poolName)
+ ? " A client cache requires a pool." : ""));
+
+ throw new BeanInitializationException(message);
+ }
+ }
+ else {
if (log.isDebugEnabled()) {
- log.debug("Checking for a unique pool");
+ log.debug("Checking for a unique pool...");
}
- p = getBeanFactory().getBean(Pool.class);
- this.poolName = p.getName();
+
+ localPool = getBeanFactory().getBean(Pool.class);
+ this.poolName = localPool.getName();
}
}
- if (p != null) {
+ if (localPool != null) {
// copy the pool settings - this way if the pool is not found, at
// least the cache will have a similar config
- ccf.setPoolFreeConnectionTimeout(p.getFreeConnectionTimeout());
- ccf.setPoolIdleTimeout(p.getIdleTimeout());
- ccf.setPoolLoadConditioningInterval(p.getLoadConditioningInterval());
- ccf.setPoolMaxConnections(p.getMaxConnections());
- ccf.setPoolMinConnections(p.getMinConnections());
- ccf.setPoolMultiuserAuthentication(p.getMultiuserAuthentication());
- ccf.setPoolPingInterval(p.getPingInterval());
- ccf.setPoolPRSingleHopEnabled(p.getPRSingleHopEnabled());
- ccf.setPoolReadTimeout(p.getReadTimeout());
- ccf.setPoolRetryAttempts(p.getRetryAttempts());
- ccf.setPoolServerGroup(p.getServerGroup());
- ccf.setPoolSocketBufferSize(p.getSocketBufferSize());
- ccf.setPoolStatisticInterval(p.getStatisticInterval());
- ccf.setPoolSubscriptionAckInterval(p.getSubscriptionAckInterval());
- ccf.setPoolSubscriptionEnabled(p.getSubscriptionEnabled());
- ccf.setPoolSubscriptionMessageTrackingTimeout(p
- .getSubscriptionMessageTrackingTimeout());
- ccf.setPoolSubscriptionRedundancy(p.getSubscriptionRedundancy());
- ccf.setPoolThreadLocalConnections(p.getThreadLocalConnections());
+ clientCacheFactory.setPoolFreeConnectionTimeout(localPool.getFreeConnectionTimeout());
+ clientCacheFactory.setPoolIdleTimeout(localPool.getIdleTimeout());
+ clientCacheFactory.setPoolLoadConditioningInterval(localPool.getLoadConditioningInterval());
+ clientCacheFactory.setPoolMaxConnections(localPool.getMaxConnections());
+ clientCacheFactory.setPoolMinConnections(localPool.getMinConnections());
+ clientCacheFactory.setPoolMultiuserAuthentication(localPool.getMultiuserAuthentication());
+ clientCacheFactory.setPoolPingInterval(localPool.getPingInterval());
+ clientCacheFactory.setPoolPRSingleHopEnabled(localPool.getPRSingleHopEnabled());
+ clientCacheFactory.setPoolReadTimeout(localPool.getReadTimeout());
+ clientCacheFactory.setPoolRetryAttempts(localPool.getRetryAttempts());
+ clientCacheFactory.setPoolServerGroup(localPool.getServerGroup());
+ clientCacheFactory.setPoolSocketBufferSize(localPool.getSocketBufferSize());
+ clientCacheFactory.setPoolStatisticInterval(localPool.getStatisticInterval());
+ clientCacheFactory.setPoolSubscriptionAckInterval(localPool.getSubscriptionAckInterval());
+ clientCacheFactory.setPoolSubscriptionEnabled(localPool.getSubscriptionEnabled());
+ clientCacheFactory.setPoolSubscriptionMessageTrackingTimeout(localPool.getSubscriptionMessageTrackingTimeout());
+ clientCacheFactory.setPoolSubscriptionRedundancy(localPool.getSubscriptionRedundancy());
+ clientCacheFactory.setPoolThreadLocalConnections(localPool.getThreadLocalConnections());
+
+ List locators = localPool.getLocators();
- List locators = p.getLocators();
if (locators != null) {
- for (InetSocketAddress inet : locators) {
- ccf.addPoolLocator(inet.getHostName(), inet.getPort());
+ for (InetSocketAddress socketAddress : locators) {
+ clientCacheFactory.addPoolLocator(socketAddress.getHostName(), socketAddress.getPort());
}
}
- List servers = p.getServers();
+ List servers = localPool.getServers();
+
if (servers != null) {
- for (InetSocketAddress inet : servers) {
- ccf.addPoolServer(inet.getHostName(), inet.getPort());
+ for (InetSocketAddress socketAddress : servers) {
+ clientCacheFactory.addPoolServer(socketAddress.getHostName(), socketAddress.getPort());
}
}
}
}
+ @Override
+ protected void applyPdxOptions(Object factory) {
+ if (factory instanceof ClientCacheFactory) {
+ new PdxOptions((ClientCacheFactory) factory).run();
+ }
+ }
+
@Override
protected void postProcessPropertiesBeforeInitialization(final Properties gemfireProperties) {
}
+ /**
+ * Inform the GemFire cluster that this client cache is ready to receive events.
+ */
+ private void readyForEvents(){
+ ClientCache clientCache = ClientCacheFactory.getAnyInstance();
+
+ if (Boolean.TRUE.equals(readyForEvents) && !clientCache.isClosed()) {
+ try {
+ clientCache.readyForEvents();
+ }
+ catch (IllegalStateException ignore) {
+ // cannot be called for a non-durable client so exception is thrown
+ }
+ }
+ }
+
@Override
public final Boolean getEnableAutoReconnect() {
return Boolean.FALSE;
@@ -231,31 +253,23 @@ public class ClientCacheFactoryBean extends CacheFactoryBean {
this.readyForEvents = readyForEvents;
}
+ /**
+ * Gets the value for the readyForEvents property.
+ *
+ * @return a boolean value indicating the state of the 'readyForEvents' property.
+ */
public Boolean getReadyForEvents(){
return this.readyForEvents;
}
@Override
- protected void applyPdxOptions(Object factory) {
- if (factory instanceof ClientCacheFactory) {
- new PdxOptions((ClientCacheFactory) factory).run();
- }
+ public final Boolean getUseSharedConfiguration() {
+ return Boolean.FALSE;
}
- /**
- * Inform the GemFire cluster that this client cache is ready to receive events.
- */
- private void readyForEvents(){
- ClientCache clientCache = ClientCacheFactory.getAnyInstance();
-
- if (Boolean.TRUE.equals(readyForEvents) && !clientCache.isClosed()) {
- try {
- clientCache.readyForEvents();
- }
- catch (IllegalStateException ignore) {
- // cannot be called for a non-durable client so exception is thrown
- }
- }
+ @Override
+ public final void setUseSharedConfiguration(Boolean useSharedConfiguration) {
+ throw new UnsupportedOperationException("Shared, cluster configuration is not applicable to clients.");
}
}
diff --git a/src/main/java/org/springframework/data/gemfire/config/CacheParser.java b/src/main/java/org/springframework/data/gemfire/config/CacheParser.java
index 258b1e67..9eb50507 100644
--- a/src/main/java/org/springframework/data/gemfire/config/CacheParser.java
+++ b/src/main/java/org/springframework/data/gemfire/config/CacheParser.java
@@ -56,6 +56,8 @@ class CacheParser extends AbstractSimpleBeanDefinitionParser {
ParsingUtils.setPropertyValue(element, builder, "cache-xml-location", "cacheXml");
ParsingUtils.setPropertyReference(element, builder, "properties-ref", "properties");
+ ParsingUtils.setPropertyValue(element, builder, "lazy-init","lazyInitialize");
+ ParsingUtils.setPropertyValue(element, builder, "use-bean-factory-locator");
ParsingUtils.setPropertyValue(element, builder, "close");
ParsingUtils.setPropertyValue(element, builder, "copy-on-read");
ParsingUtils.setPropertyValue(element, builder, "critical-heap-percentage");
@@ -70,8 +72,7 @@ class CacheParser extends AbstractSimpleBeanDefinitionParser {
ParsingUtils.setPropertyValue(element, builder, "pdx-persistent");
parsePdxDiskStore(element, parserContext, builder);
ParsingUtils.setPropertyValue(element, builder, "search-timeout");
- ParsingUtils.setPropertyValue(element, builder, "lazy-init","lazyInitialize");
- ParsingUtils.setPropertyValue(element, builder, "use-bean-factory-locator");
+ ParsingUtils.setPropertyValue(element, builder, "use-shared-configuration");
List txListeners = DomUtils.getChildElementsByTagName(element, "transaction-listener");
diff --git a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.5.xsd b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.5.xsd
index 5ebac4f9..57f4f2a7 100644
--- a/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.5.xsd
+++ b/src/main/resources/org/springframework/data/gemfire/config/spring-gemfire-1.5.xsd
@@ -274,20 +274,19 @@ Defines a GemFire Cache instance used for creating or retrieving 'regions'.
@@ -329,6 +328,23 @@ You may want to change this based on your knowledge of the network load or other
]]>
+
+
+
+
+
diff --git a/src/test/java/org/springframework/data/gemfire/CacheUsingSharedConfigurationIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/CacheUsingSharedConfigurationIntegrationTest.java
new file mode 100644
index 00000000..fb8cc66a
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/CacheUsingSharedConfigurationIntegrationTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Resource;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.data.gemfire.fork.LocatorProcess;
+import org.springframework.data.gemfire.process.ProcessExecutor;
+import org.springframework.data.gemfire.process.ProcessWrapper;
+import org.springframework.data.gemfire.test.support.ThreadUtils;
+import org.springframework.data.gemfire.test.support.ZipUtils;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.util.FileSystemUtils;
+
+import com.gemstone.gemfire.cache.DataPolicy;
+import com.gemstone.gemfire.cache.Region;
+import com.gemstone.gemfire.cache.Scope;
+
+/**
+ * The CacheUsingSharedConfigurationIntegrationTest class is a test suite of test cases testing the integration of
+ * Spring Data GemFire with GemFire 8's new shared, persistent, cluster configuration service.
+ *
+ * @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.5.0
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("cacheUsingSharedConfigurationIntegrationTest.xml")
+@SuppressWarnings("unused")
+public class CacheUsingSharedConfigurationIntegrationTest {
+
+ @Resource(name = "SharedConfigRegion")
+ private Region sharedConfigRegion;
+
+ @Resource(name = "NativeLocalRegion")
+ private Region nativeLocalRegion;
+
+ @Resource(name = "NativePartitionRegion")
+ private Region nativePartitionRegion;
+
+ @Resource(name = "NativeReplicateRegion")
+ private Region nativeReplicateRegion;
+
+ @Resource(name = "LocalRegion")
+ private Region localRegion;
+
+ private static File locatorWorkingDirectory;
+
+ private static ProcessWrapper locatorProcess;
+
+ @BeforeClass
+ public static void testSuiteSetup() throws IOException {
+ String locatorName = "SharedConfigLocator";
+
+ locatorWorkingDirectory = new File(System.getProperty("user.dir"), locatorName.toLowerCase());
+
+ assertTrue(locatorWorkingDirectory.isDirectory() || locatorWorkingDirectory.mkdirs());
+
+ ZipUtils.unzip(new ClassPathResource("/shared_config.zip"), locatorWorkingDirectory);
+
+ List arguments = new ArrayList();
+
+ arguments.add("-Dgemfire.name=" + locatorName);
+ arguments.add("-Dspring.gemfire.enable-shared-configuration=true");
+ arguments.add("-Dspring.gemfire.load-shared-configuration=true");
+
+ locatorProcess = ProcessExecutor.launch(locatorWorkingDirectory, LocatorProcess.class,
+ arguments.toArray(new String[arguments.size()]));
+
+ locatorProcess.registerShutdownHook();
+
+ waitForLocatorStart(TimeUnit.SECONDS.toMillis(30));
+
+ //System.out.println("Shared Configuration Locator should be running!");
+ }
+
+ private static void waitForLocatorStart(final long milliseconds) {
+ ThreadUtils.timedWait(milliseconds, 500, new ThreadUtils.WaitCondition() {
+ File pidControlFile = new File(locatorWorkingDirectory, LocatorProcess.getLocatorProcessControlFilename());
+ @Override public boolean waiting() {
+ return !pidControlFile.isFile();
+ }
+ });
+ }
+
+ @AfterClass
+ public static void testSuiteTearDown() {
+ locatorProcess.shutdown();
+ FileSystemUtils.deleteRecursively(locatorWorkingDirectory);
+ }
+
+ protected Region assertRegion(final Region actualRegion, final String expectedRegionName) {
+ return assertRegion(actualRegion, expectedRegionName, Region.SEPARATOR+expectedRegionName);
+ }
+
+ protected Region assertRegion(final Region actualRegion, final String expectedRegionName, final String expectedRegionFullPath) {
+ assertNotNull(String.format("The '%1$s' was not properly configured and initialized!", expectedRegionName), actualRegion);
+ assertEquals(expectedRegionName, actualRegion.getName());
+ assertEquals(expectedRegionFullPath, actualRegion.getFullPath());
+ return actualRegion;
+ }
+
+ protected Region assertRegionAttributes(final Region actualRegion, final DataPolicy expectedDataPolicy, final Scope expectedScope) {
+ assertNotNull(actualRegion);
+ assertNotNull(actualRegion.getAttributes());
+ assertEquals(expectedDataPolicy, actualRegion.getAttributes().getDataPolicy());
+ assertEquals(expectedScope, actualRegion.getAttributes().getScope());
+ return actualRegion;
+ }
+
+ @Test
+ public void testConfiguration() {
+ assertRegionAttributes(assertRegion(sharedConfigRegion, "SharedConfigRegion"),
+ DataPolicy.PARTITION, Scope.DISTRIBUTED_NO_ACK);
+
+ assertRegionAttributes(assertRegion(nativeLocalRegion, "NativeLocalRegion"), DataPolicy.NORMAL, Scope.LOCAL);
+
+ assertRegionAttributes(assertRegion(nativePartitionRegion, "NativePartitionRegion"),
+ DataPolicy.PARTITION, Scope.DISTRIBUTED_NO_ACK);
+
+ assertRegionAttributes(assertRegion(nativeReplicateRegion, "NativeReplicateRegion"),
+ DataPolicy.REPLICATE, Scope.DISTRIBUTED_ACK);
+
+ assertRegionAttributes(assertRegion(localRegion, "LocalRegion"), DataPolicy.NORMAL, Scope.LOCAL);
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/gemfire/ForkUtil.java b/src/test/java/org/springframework/data/gemfire/ForkUtil.java
index 97fec026..6fd944d3 100644
--- a/src/test/java/org/springframework/data/gemfire/ForkUtil.java
+++ b/src/test/java/org/springframework/data/gemfire/ForkUtil.java
@@ -27,6 +27,9 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import org.springframework.data.gemfire.fork.CacheServerProcess;
+import org.springframework.data.gemfire.test.support.IOUtils;
+import org.springframework.data.gemfire.test.support.ThreadUtils;
import org.springframework.util.StringUtils;
/**
@@ -34,7 +37,9 @@ import org.springframework.util.StringUtils;
*
* @author Costin Leau
* @author John Blum
+ * @deprecated ForkUtils has serious design flaws; please use ProcessExecutor instead
*/
+@Deprecated
public class ForkUtil {
private static OutputStream processStandardInStream;
@@ -47,13 +52,11 @@ public class ForkUtil {
private static OutputStream cloneJVM(final String arguments) {
String[] args = arguments.split("\\s+");
- List command = new ArrayList(args.length + 5);
+ List command = new ArrayList(args.length + 3);
command.add(JAVA_EXE);
command.add("-classpath");
command.add(JAVA_CLASSPATH);
- //command.add("-Xms256m");
- //command.add("-Xmx512m");
command.addAll(Arrays.asList(args));
Process javaProcess;
@@ -72,8 +75,8 @@ public class ForkUtil {
final AtomicBoolean runCondition = new AtomicBoolean(true);
final Process p = javaProcess;
- new Thread(newProcessStreamReaderRunnable(runCondition, p.getErrorStream(), "[FORK-ERROR]")).start();
- new Thread(newProcessStreamReaderRunnable(runCondition, p.getInputStream(), "[FORK]")).start();
+ startNewThread(newProcessStreamReader(runCondition, p.getErrorStream(), "[FORK-ERROR]"));
+ startNewThread(newProcessStreamReader(runCondition, p.getInputStream(), "[FORK]"));
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
@@ -98,61 +101,69 @@ public class ForkUtil {
return processStandardInStream;
}
- protected static Runnable newProcessStreamReaderRunnable(final AtomicBoolean runCondition, final InputStream processStream, final String label) {
- final BufferedReader processStreamReader = new BufferedReader(new InputStreamReader(processStream));
+ protected static Thread startNewThread(final Runnable runnable) {
+ Thread runnableThread = new Thread(runnable);
+ runnableThread.start();
+ return runnableThread;
+ }
+ protected static Runnable newProcessStreamReader(final AtomicBoolean runCondition, final InputStream processStream, final String label) {
return new Runnable() {
public void run() {
+ BufferedReader processStreamReader = new BufferedReader(new InputStreamReader(processStream));
try {
- do {
+ while (runCondition.get()) {
for (String line = "Reading..."; line != null; line = processStreamReader.readLine()) {
System.out.printf("%1$s %2$s%n ", label, line);
}
-
- Thread.sleep(200);
}
- while (runCondition.get());
}
catch (Exception ignore) {
}
+ finally {
+ IOUtils.close(processStreamReader);
+ }
}
};
}
- public static OutputStream cacheServer(Class> clazz) {
- return startCacheServer(clazz.getName());
- }
-
public static OutputStream cacheServer() {
- return startCacheServer("org.springframework.data.gemfire.fork.CacheServerProcess");
+ return cacheServer(CacheServerProcess.class);
}
-
+
+ public static OutputStream cacheServer(Class> type) {
+ return startCacheServer(type.getName());
+ }
+
public static OutputStream startCacheServer(String args) {
+ return startGemFireProcess(args, "cache server");
+ }
+
+ protected static OutputStream startGemFireProcess(final String args, final String processName) {
String className = args.split(" ")[0];
-
- System.out.println("main class:" + className);
-
+
+ System.out.println("main class: " + className);
+
if (controlFileExists(className)) {
deleteControlFile(className);
}
- OutputStream os = cloneJVM(args);
- int maxTime = 30000;
- int time = 0;
- while (!controlFileExists(className) && time < maxTime) {
- try {
- Thread.sleep(500);
- time += 500;
- } catch (InterruptedException ex) {
- // ignore and move on
- }
+
+ OutputStream outputStream = cloneJVM(args);
+
+ final long timeout = System.currentTimeMillis() + 30000;
+
+ while (!controlFileExists(className) && System.currentTimeMillis() < timeout) {
+ ThreadUtils.sleep(500);
}
+
if (controlFileExists(className)) {
- System.out.println("[FORK] Started cache server");
+ System.out.printf("[FORK] Started %1$s%n", processName);
}
else {
- throw new RuntimeException("could not fork cache server");
+ throw new RuntimeException(String.format("Failed to fork %1$s", processName));
}
- return os;
+
+ return outputStream;
}
public static void sendSignal() {
@@ -165,19 +176,16 @@ public class ForkUtil {
}
}
- public static boolean deleteControlFile(String name) {
- String path = TEMP_DIRECTORY + File.separator + name;
- return new File(path).delete();
+ public static boolean createControlFile(final String name) throws IOException {
+ return new File(TEMP_DIRECTORY + File.separator + name).createNewFile();
}
- public static boolean createControlFile(String name) throws IOException {
- String path = TEMP_DIRECTORY + File.separator + name;
- return new File(path).createNewFile();
+ public static boolean controlFileExists(final String name) {
+ return new File(TEMP_DIRECTORY + File.separator + name).isFile();
}
- public static boolean controlFileExists(String name) {
- String path = TEMP_DIRECTORY + File.separator + name;
- return new File(path).exists();
+ public static boolean deleteControlFile(final String name) {
+ return new File(TEMP_DIRECTORY + File.separator + name).delete();
}
}
diff --git a/src/test/java/org/springframework/data/gemfire/config/GemfireV7GatewayNamespaceTest.java b/src/test/java/org/springframework/data/gemfire/config/GemfireV7GatewayNamespaceTest.java
index db8bd795..145a6800 100644
--- a/src/test/java/org/springframework/data/gemfire/config/GemfireV7GatewayNamespaceTest.java
+++ b/src/test/java/org/springframework/data/gemfire/config/GemfireV7GatewayNamespaceTest.java
@@ -74,7 +74,7 @@ public class GemfireV7GatewayNamespaceTest extends RecreatingContextTest {
@Before
@Override
public void createCtx() {
- if (GemfireUtils.GEMFIRE_VERSION.startsWith("7")) {
+ if (GemfireUtils.isGemfireVersion7OrAbove()) {
super.createCtx();
}
}
diff --git a/src/test/java/org/springframework/data/gemfire/fork/CacheServerProcess.java b/src/test/java/org/springframework/data/gemfire/fork/CacheServerProcess.java
index e2f29b12..ffad0eca 100644
--- a/src/test/java/org/springframework/data/gemfire/fork/CacheServerProcess.java
+++ b/src/test/java/org/springframework/data/gemfire/fork/CacheServerProcess.java
@@ -38,12 +38,12 @@ import com.gemstone.gemfire.cache.server.CacheServer;
public class CacheServerProcess {
public static void main(final String[] args) throws Exception {
- Properties props = new Properties();
- props.setProperty("name", "CqServer");
- props.setProperty("mcast-port", "0");
- props.setProperty("log-level", "warning");
+ Properties gemfireProperties = new Properties();
+ gemfireProperties.setProperty("name", "CqServer");
+ gemfireProperties.setProperty("mcast-port", "0");
+ gemfireProperties.setProperty("log-level", "warning");
- Cache cache = new CacheFactory(props).create();
+ Cache cache = new CacheFactory(gemfireProperties).create();
RegionFactory regionFactory = cache.createRegionFactory();
regionFactory.setDataPolicy(DataPolicy.REPLICATE);
@@ -73,6 +73,8 @@ public class CacheServerProcess {
System.out.println("Waiting for shutdown...");
bufferedReader.readLine();
+
+ System.out.println("Shutting down!");
}
}
diff --git a/src/test/java/org/springframework/data/gemfire/fork/LocatorProcess.java b/src/test/java/org/springframework/data/gemfire/fork/LocatorProcess.java
new file mode 100644
index 00000000..e1336d4c
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/fork/LocatorProcess.java
@@ -0,0 +1,129 @@
+/*
+ * 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.fork;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.data.gemfire.process.support.ProcessUtils;
+import org.springframework.data.gemfire.test.support.ThreadUtils;
+
+import com.gemstone.gemfire.distributed.LocatorLauncher;
+import com.gemstone.gemfire.distributed.internal.DistributionConfig;
+import com.gemstone.gemfire.distributed.internal.InternalLocator;
+import com.gemstone.gemfire.distributed.internal.SharedConfiguration;
+
+/**
+ * The LocatorProcess class is a main Java class that is used fork and launch a GemFire Locator process using the
+ * LocatorLauncher class.
+ *
+ * @author John Blum
+ * @see com.gemstone.gemfire.distributed.LocatorLauncher
+ * @since 1.5.0
+ */
+public class LocatorProcess {
+
+ public static final int DEFAULT_LOCATOR_PORT = 20668;
+
+ public static final String DEFAULT_GEMFIRE_MEMBER_NAME = "SpringDataGemFire-Locator";
+ public static final String DEFAULT_HOSTNAME_FOR_CLIENTS = "localhost";
+ public static final String DEFAULT_HTTP_SERVICE_PORT = "0";
+ public static final String DEFAULT_LOG_LEVEL = "config";
+
+ public static void main(final String... args) throws IOException {
+ LocatorLauncher locatorLauncher = buildLocatorLauncher();
+
+ registerShutdownHook();
+
+ // start the GemFire Locator process...
+ locatorLauncher.start();
+
+ waitForLocatorStart(TimeUnit.SECONDS.toMillis(20));
+
+ ProcessUtils.writePid(new File(System.getProperty("user.dir"), getLocatorProcessControlFilename()),
+ ProcessUtils.currentPid());
+
+ ProcessUtils.waitForStopSignal();
+ }
+
+ public static String getLocatorProcessControlFilename() {
+ return LocatorProcess.class.getSimpleName().toLowerCase().concat(".pid");
+ }
+
+ private static LocatorLauncher buildLocatorLauncher() {
+ return new LocatorLauncher.Builder()
+ .setMemberName(DEFAULT_GEMFIRE_MEMBER_NAME)
+ .setHostnameForClients(System.getProperty("spring.gemfire.hostname-for-clients",
+ DEFAULT_HOSTNAME_FOR_CLIENTS))
+ .setPort(Integer.getInteger("spring.gemfire.locator-port", DEFAULT_LOCATOR_PORT))
+ .setRedirectOutput(false)
+ .set(DistributionConfig.ENABLE_SHARED_CONFIGURATION_NAME, String.valueOf(Boolean.getBoolean(
+ "spring.gemfire.enable-shared-configuration")))
+ .set(DistributionConfig.HTTP_SERVICE_PORT_NAME, System.getProperty("spring.gemfire.http-service-port",
+ DEFAULT_HTTP_SERVICE_PORT))
+ .set(DistributionConfig.JMX_MANAGER_NAME, String.valueOf(Boolean.TRUE))
+ .set(DistributionConfig.JMX_MANAGER_START_NAME, String.valueOf(Boolean.FALSE))
+ .set(DistributionConfig.LOAD_SHARED_CONFIG_FROM_DIR_NAME, String.valueOf(Boolean.getBoolean(
+ "spring.gemfire.load-shared-configuration")))
+ .set(DistributionConfig.LOG_LEVEL_NAME, System.getProperty("spring.gemfire.log-level", DEFAULT_LOG_LEVEL))
+ .build();
+ }
+
+ private static boolean isSharedConfigurationEnabled(final InternalLocator locator) {
+ return (locator != null && Boolean.valueOf(locator.getDistributedSystem().getProperties().getProperty(
+ DistributionConfig.ENABLE_SHARED_CONFIGURATION_NAME)));
+ }
+
+ private static void registerShutdownHook() {
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override public void run() {
+ stopSharedConfigurationService();
+ LocatorLauncher.getInstance().stop();
+ }
+
+ private void stopSharedConfigurationService() {
+ InternalLocator locator = InternalLocator.getLocator();
+
+ if (isSharedConfigurationEnabled(locator)) {
+ SharedConfiguration sharedConfiguration = locator.getSharedConfiguration();
+
+ if (sharedConfiguration != null) {
+ sharedConfiguration.destroySharedConfiguration();
+ }
+ }
+ }
+ }));
+ }
+
+ private static void waitForLocatorStart(final long milliseconds) {
+ final InternalLocator locator = InternalLocator.getLocator();
+
+ if (isSharedConfigurationEnabled(locator)) {
+ ThreadUtils.timedWait(milliseconds, 500, new ThreadUtils.WaitCondition() {
+ @Override public boolean waiting() {
+ return !locator.isSharedConfigurationRunning();
+ }
+ });
+ }
+ else {
+ LocatorLauncher.getInstance().waitOnStatusResponse(milliseconds, Math.min(500, milliseconds),
+ TimeUnit.MILLISECONDS);
+ }
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/gemfire/process/ProcessConfiguration.java b/src/test/java/org/springframework/data/gemfire/process/ProcessConfiguration.java
new file mode 100644
index 00000000..10d4b928
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/process/ProcessConfiguration.java
@@ -0,0 +1,100 @@
+/*
+ * 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.process;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * The ProcessContext class is a container encapsulating configuration and context meta-data for a running process.
+ *
+ * @author John Blum
+ * @see java.lang.ProcessBuilder
+ * @since 1.5.0
+ */
+@SuppressWarnings("unused")
+public class ProcessConfiguration {
+
+ private final boolean redirectingErrorStream;
+
+ private final File workingDirectory;
+
+ private final List command;
+
+ private final Map environment;
+
+ public static ProcessConfiguration create(final ProcessBuilder processBuilder) {
+ Assert.notNull(processBuilder, "The ProcessBuilder used to construct, configure and start the Process must not be null!");
+
+ return new ProcessConfiguration(processBuilder.command(), processBuilder.directory(),
+ processBuilder.environment(), processBuilder.redirectErrorStream());
+ }
+
+ public ProcessConfiguration(final List command, final File workingDirectory,
+ final Map environment, final boolean redirectingErrorStream) {
+
+ Assert.notEmpty(command, "The command used to run the process must be specified!");
+
+ Assert.isTrue((workingDirectory != null && workingDirectory.isDirectory()), String.format(
+ "The process working directory (%1$s) is not valid!", workingDirectory));
+
+ this.command = new ArrayList(command);
+ this.workingDirectory = workingDirectory;
+ this.redirectingErrorStream = redirectingErrorStream;
+
+ this.environment = (environment != null
+ ? Collections.unmodifiableMap(new HashMap(environment))
+ : Collections.emptyMap());
+ }
+
+ public List getCommand() {
+ return Collections.unmodifiableList(command);
+ }
+
+ public String getCommandString() {
+ return StringUtils.arrayToDelimitedString(getCommand().toArray(), " ");
+ }
+
+ public Map getEnvironment() {
+ return environment;
+ }
+
+ public boolean isRedirectingErrorStream() {
+ return redirectingErrorStream;
+ }
+
+ public File getWorkingDirectory() {
+ return workingDirectory;
+ }
+
+ @Override
+ public String toString() {
+ return "{ command = ".concat(getCommandString())
+ .concat(", workingDirectory = ".concat(getWorkingDirectory().getAbsolutePath()))
+ .concat(", redirectingErrorStream = ".concat(String.valueOf(isRedirectingErrorStream())))
+ .concat(", environment = ".concat(String.valueOf(getEnvironment())))
+ .concat(" }");
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/gemfire/process/ProcessExecutor.java b/src/test/java/org/springframework/data/gemfire/process/ProcessExecutor.java
new file mode 100644
index 00000000..671f39a6
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/process/ProcessExecutor.java
@@ -0,0 +1,116 @@
+/*
+ * 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.process;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * The ProcessLauncher class is a utility class for launching Java processes.
+ *
+ * @author John Blum
+ * @see java.lang.Process
+ * @see java.lang.ProcessBuilder
+ * @see org.springframework.data.gemfire.process.ProcessConfiguration
+ * @see org.springframework.data.gemfire.process.ProcessWrapper
+ * @since 1.5.0
+ */
+@SuppressWarnings("unused")
+public abstract class ProcessExecutor {
+
+ protected static final String JAVA_CLASSPATH = System.getProperty("java.class.path");
+
+ protected static final String JAVA_HOME = System.getProperty("java.home");
+
+ protected static final String JAVA_EXE = JAVA_HOME.concat(File.separator).concat("bin")
+ .concat(File.separator).concat("java");
+
+ protected static final String USER_HOME = System.getProperty("user.home");
+
+ protected static final String USER_WORKING_DIRECTORY = System.getProperty("user.dir");
+
+ public static ProcessWrapper launch(final Class> type, final String... args) throws IOException {
+ return launch(new File(USER_WORKING_DIRECTORY), type, args);
+ }
+
+ public static ProcessWrapper launch(final File workingDirectory, final Class> type, final String... args)
+ throws IOException
+ {
+ ProcessBuilder processBuilder = new ProcessBuilder()
+ .command(buildCommand(type, args))
+ .directory(validateDirectory(workingDirectory))
+ .redirectErrorStream(true);
+
+ Process process = processBuilder.start();
+
+ ProcessWrapper processWrapper = new ProcessWrapper(process, ProcessConfiguration.create(processBuilder));
+
+ processWrapper.register(new ProcessInputStreamListener() {
+ @Override public void onInput(final String input) {
+ System.err.printf("[FORK-OUT] - %1$s%n", input);
+ }
+ });
+
+ return processWrapper;
+ }
+
+ protected static String[] buildCommand(final Class> type, final String... args) {
+ Assert.notNull(type != null, "The main Class to launch must not be null!");
+
+ List command = new ArrayList();
+ List programArgs = Collections.emptyList();
+
+ command.add(JAVA_EXE);
+ command.add("-server");
+ command.add("-classpath");
+ command.add(JAVA_CLASSPATH);
+
+ if (args != null) {
+ programArgs = new ArrayList(args.length);
+
+ for (String arg : args) {
+ if (isJvmOption(arg)) {
+ command.add(arg);
+ }
+ else if (!StringUtils.isEmpty(arg)) {
+ programArgs.add(arg);
+ }
+ }
+ }
+
+ command.add(type.getName());
+ command.addAll(programArgs);
+
+ return command.toArray(new String[command.size()]);
+ }
+
+ protected static boolean isJvmOption(final String option) {
+ return (!StringUtils.isEmpty(option) && (option.startsWith("-D") || option.startsWith("-X")));
+ }
+
+ protected static File validateDirectory(final File workingDirectory) {
+ Assert.isTrue(workingDirectory != null && (workingDirectory.isDirectory() || workingDirectory.mkdirs()));
+ return workingDirectory;
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/gemfire/process/ProcessInputStreamListener.java b/src/test/java/org/springframework/data/gemfire/process/ProcessInputStreamListener.java
new file mode 100644
index 00000000..75a4c9dc
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/process/ProcessInputStreamListener.java
@@ -0,0 +1,33 @@
+/*
+ * 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.process;
+
+import java.util.EventListener;
+
+/**
+ * The ProcessStreamListener interface is a callback listener that gets called when input arrives from either a
+ * process's standard output steam or standard error stream.
+ *
+ * @author John Blum
+ * @see java.util.EventListener
+ * @since 1.5.0
+ */
+public interface ProcessInputStreamListener extends EventListener {
+
+ void onInput(String input);
+
+}
diff --git a/src/test/java/org/springframework/data/gemfire/process/ProcessWrapper.java b/src/test/java/org/springframework/data/gemfire/process/ProcessWrapper.java
new file mode 100644
index 00000000..770e6e7f
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/process/ProcessWrapper.java
@@ -0,0 +1,274 @@
+/*
+ * 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.process;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.springframework.data.gemfire.process.support.PidUnavailableException;
+import org.springframework.data.gemfire.process.support.ProcessUtils;
+import org.springframework.data.gemfire.test.support.IOUtils;
+import org.springframework.data.gemfire.test.support.ThreadUtils;
+import org.springframework.data.gemfire.test.support.ThrowableUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * The ProcessWrapper class is a wrapper for a Process object representing an OS process and the ProcessBuilder used
+ * to construct and start the process.
+ *
+ * @author John Blum
+ * @see java.lang.Process
+ * @see java.lang.ProcessBuilder
+ * @since 1.5.0
+ */
+@SuppressWarnings("unused")
+public class ProcessWrapper {
+
+ protected static final boolean DEFAULT_DAEMON_THREAD = true;
+
+ protected static final long DEFAULT_WAIT_TIME_MILLISECONDS = TimeUnit.SECONDS.toMillis(15);
+
+ private final List listeners = new CopyOnWriteArrayList();
+
+ protected final Logger log = Logger.getLogger(getClass().getName());
+
+ private final Process process;
+ private final ProcessConfiguration processConfiguration;
+
+ public ProcessWrapper(final Process process, final ProcessConfiguration processConfiguration) {
+ Assert.notNull(process, "The Process object backing this wrapper must not be null!");
+
+ Assert.notNull(processConfiguration, "The context and configuration meta-data providing details about"
+ + " the environment in which the process is running and how the process was configured must not be null!");
+
+ this.process = process;
+ this.processConfiguration = processConfiguration;
+
+ postInit();
+ }
+
+ private void postInit() {
+ newThread("Process OUT Stream Reader", newProcessInputStreamReader(process.getInputStream())).start();
+
+ if (!isRedirectingErrorStream()) {
+ newThread("Process ERR Stream Reader", newProcessInputStreamReader(process.getErrorStream())).start();
+ }
+ }
+
+ protected Runnable newProcessInputStreamReader(final InputStream in) {
+ return new Runnable() {
+ @Override public void run() {
+ if (isRunning()) {
+ BufferedReader inputReader = new BufferedReader(new InputStreamReader(in));
+
+ try {
+ for (String input = inputReader.readLine(); input != null; input = inputReader.readLine()) {
+ for (ProcessInputStreamListener listener : listeners) {
+ listener.onInput(input);
+ }
+ }
+ }
+ catch (IOException ignore) {
+ // ignore IO error and just stop reading from the process input stream
+ // IO error occurred most likely because the process was terminated
+ }
+ finally {
+ IOUtils.close(inputReader);
+ }
+ }
+ }
+ };
+ }
+
+ protected Thread newThread(final String name, final Runnable task) {
+ Assert.isTrue(!StringUtils.isEmpty(name), "The name of the Thread must be specified!");
+ Assert.notNull(task, "The Thread task must not be null!");
+ Thread thread = new Thread(task, name);
+ thread.setDaemon(DEFAULT_DAEMON_THREAD);
+ thread.setPriority(Thread.NORM_PRIORITY);
+ return thread;
+ }
+
+ public List getCommand() {
+ return processConfiguration.getCommand();
+ }
+
+ public String getCommandString() {
+ return processConfiguration.getCommandString();
+ }
+
+ public Map getEnvironment() {
+ return processConfiguration.getEnvironment();
+ }
+
+ public int getPid() {
+ return ProcessUtils.findAndReadPid(getWorkingDirectory());
+ }
+
+ public int safeGetPid() {
+ try {
+ return getPid();
+ }
+ catch (PidUnavailableException ignore) {
+ return -1;
+ }
+ }
+
+ public boolean isRedirectingErrorStream() {
+ return processConfiguration.isRedirectingErrorStream();
+ }
+
+ public boolean isRunning() {
+ return ProcessUtils.isRunning(this.process);
+ }
+
+ public File getWorkingDirectory() {
+ return processConfiguration.getWorkingDirectory();
+ }
+
+ public int exitValue() {
+ return process.exitValue();
+ }
+
+ public int safeExitValue() {
+ try {
+ return exitValue();
+ }
+ catch (IllegalThreadStateException ignore) {
+ return -1;
+ }
+ }
+
+ public boolean register(final ProcessInputStreamListener listener) {
+ return (listener != null && listeners.add(listener));
+ }
+
+ public void registerShutdownHook() {
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+ @Override public void run() {
+ shutdown();
+ }
+ }));
+ }
+
+ public void signalStop() {
+ try {
+ ProcessUtils.signalStop(this.process);
+ }
+ catch (IOException e) {
+ log.warning("Failed to signal the process to stop!");
+
+ if (log.isLoggable(Level.FINE)) {
+ log.fine(ThrowableUtils.toString(e));
+ }
+ }
+ }
+
+ public int stop() {
+ return stop(DEFAULT_WAIT_TIME_MILLISECONDS);
+ }
+
+ public int stop(final long milliseconds) {
+ if (isRunning()) {
+ int exitValue = -1;
+ final int pid = safeGetPid();
+ final long timeout = (System.currentTimeMillis() + milliseconds);
+ final AtomicBoolean exited = new AtomicBoolean(false);
+
+ ExecutorService executorService = Executors.newSingleThreadExecutor();
+
+ try {
+ Future futureExitValue = executorService.submit(new Callable() {
+ @Override public Integer call() throws Exception {
+ process.destroy();
+ int exitValue = process.waitFor();
+ exited.set(true);
+ return exitValue;
+ }
+ });
+
+ while (!exited.get() && System.currentTimeMillis() < timeout) {
+ try {
+ exitValue = futureExitValue.get(milliseconds, TimeUnit.MILLISECONDS);
+ log.info(String.format("Process [%1$s] has been stopped.%n", pid));
+ }
+ catch (InterruptedException ignore) {
+ }
+ }
+ }
+ catch (TimeoutException e) {
+ exitValue = -1;
+ log.warning(String.format("Process [%1$d] did not stop within the allotted timeout of %2$d seconds.%n",
+ pid, TimeUnit.MILLISECONDS.toSeconds(milliseconds)));
+ }
+ catch (Exception ignore) {
+ // handles CancellationException, ExecutionException
+ }
+ finally {
+ executorService.shutdownNow();
+ }
+
+ return exitValue;
+ }
+ else {
+ return exitValue();
+ }
+ }
+
+ public int shutdown() {
+ if (isRunning()) {
+ log.info(String.format("Stopping process [%1$d]...%n", safeGetPid()));
+ signalStop();
+ waitFor();
+ }
+
+ return stop();
+ }
+
+ public boolean unregister(final ProcessInputStreamListener listener) {
+ return listeners.remove(listener);
+ }
+
+ public void waitFor() {
+ waitFor(DEFAULT_WAIT_TIME_MILLISECONDS);
+ }
+
+ public void waitFor(final long milliseconds) {
+ ThreadUtils.timedWait(milliseconds, 500, new ThreadUtils.WaitCondition() {
+ @Override public boolean waiting() {
+ return isRunning();
+ }
+ });
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/gemfire/process/support/PidUnavailableException.java b/src/test/java/org/springframework/data/gemfire/process/support/PidUnavailableException.java
new file mode 100644
index 00000000..ef91f557
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/process/support/PidUnavailableException.java
@@ -0,0 +1,45 @@
+/*
+ * 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.process.support;
+
+/**
+ * The PidUnavailableException class is a RuntimeException indicating that the process ID (PID) is unobtainable for
+ * the current process.
+ *
+ * @author John Blum
+ * @see java.lang.RuntimeException
+ * @since 1.5.0
+ */
+@SuppressWarnings("unused")
+public class PidUnavailableException extends RuntimeException {
+
+ public PidUnavailableException() {
+ }
+
+ public PidUnavailableException(final String message) {
+ super(message);
+ }
+
+ public PidUnavailableException(final Throwable cause) {
+ super(cause);
+ }
+
+ public PidUnavailableException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/gemfire/process/support/ProcessUtils.java b/src/test/java/org/springframework/data/gemfire/process/support/ProcessUtils.java
new file mode 100644
index 00000000..71ef2443
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/process/support/ProcessUtils.java
@@ -0,0 +1,211 @@
+/*
+ * 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.process.support;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.util.Scanner;
+import java.util.logging.Logger;
+
+import org.springframework.data.gemfire.test.support.IOUtils;
+import org.springframework.util.StringUtils;
+
+import com.gemstone.gemfire.management.internal.cli.util.spring.Assert;
+import com.sun.tools.attach.VirtualMachine;
+import com.sun.tools.attach.VirtualMachineDescriptor;
+
+/**
+ * The ProcessUtils class is a utilty class for working with process, or specifically instances
+ * of the Java Process class.
+ *
+ * @author John Blum
+ * @see java.lang.Process
+ * @see java.lang.management.RuntimeMXBean
+ * @see com.sun.tools.attach.VirtualMachine
+ * @since 1.5.0
+ */
+@SuppressWarnings("unused")
+public abstract class ProcessUtils {
+
+ protected static final Logger log = Logger.getLogger(ProcessUtils.class.getName());
+
+ protected static final String TERM_TOKEN = "";
+
+ public static int currentPid() {
+ RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
+ String runtimeMXBeanName = runtimeMXBean.getName();
+
+ Exception cause = null;
+
+ if (StringUtils.hasText(runtimeMXBeanName)) {
+ int atSignIndex = runtimeMXBeanName.indexOf('@');
+
+ if (atSignIndex > 0) {
+ try {
+ return Integer.parseInt(runtimeMXBeanName.substring(0, atSignIndex));
+ }
+ catch (NumberFormatException e) {
+ cause = e;
+ }
+ }
+ }
+
+ throw new PidUnavailableException(String.format("The process ID (PID) is not available (%1$s)!",
+ runtimeMXBeanName), cause);
+ }
+
+ public static boolean isRunning(final int processId) {
+ for (VirtualMachineDescriptor vmDescriptor : VirtualMachine.list()) {
+ if (String.valueOf(processId).equals(vmDescriptor.id())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static boolean isRunning(final Process process) {
+ try {
+ process.exitValue();
+ return false;
+ }
+ catch (IllegalThreadStateException ignore) {
+ return true;
+ }
+ }
+
+ public static void signalStop(final Process process) throws IOException {
+ if (isRunning(process)) {
+ OutputStream processOutputStream = process.getOutputStream();
+ processOutputStream.write(TERM_TOKEN.concat("\n").getBytes());
+ processOutputStream.flush();
+ }
+ }
+
+ public static void waitForStopSignal() {
+ Scanner in = new Scanner(System.in);
+ while (!TERM_TOKEN.equals(in.next()));
+ }
+
+ public static int findAndReadPid(final File workingDirectory) {
+ Assert.isTrue(workingDirectory != null && workingDirectory.isDirectory(), String.format(
+ "The file system pathname (%1$s) expected to contain a PID file is not a valid directory!",
+ workingDirectory));
+
+ File pidFile = findPidFile(workingDirectory);
+
+ if (pidFile == null) {
+ throw new PidUnavailableException(String.format(
+ "No PID file was found in working directory (%1$s) or any of it's sub-directories!",
+ workingDirectory));
+ }
+
+ return readPid(pidFile);
+ }
+
+ protected static File findPidFile(final File workingDirectory) {
+ Assert.isTrue(workingDirectory != null && workingDirectory.isDirectory(), String.format(
+ "The file system pathname (%1$s) is not valid directory!", workingDirectory));
+
+ for (File file : workingDirectory.listFiles(DirectoryPidFileFilter.INSTANCE)) {
+ if (file.isDirectory()) {
+ file = findPidFile(file);
+ }
+
+ if (PidFileFilter.INSTANCE.accept(file)) {
+ return file;
+ }
+ }
+
+ return null;
+ }
+
+ public static int readPid(final File pidFile) {
+ Assert.isTrue(pidFile != null && pidFile.isFile(), String.format(
+ "The file system pathname (%1$s) is not a valid file!", pidFile));
+
+ BufferedReader fileReader = null;
+ String pidValue = null;
+
+ try {
+ fileReader = new BufferedReader(new FileReader(pidFile));
+ pidValue = String.valueOf(fileReader.readLine()).trim();
+ return Integer.parseInt(pidValue);
+ }
+ catch (FileNotFoundException e) {
+ throw new PidUnavailableException(String.format("PID file (%1$s) could not be found!", pidFile), e);
+ }
+ catch (IOException e) {
+ throw new PidUnavailableException(String.format("Unable to read PID from file (%1$s)!", pidFile), e);
+ }
+ catch (NumberFormatException e) {
+ throw new PidUnavailableException(String.format(
+ "The value (%1$s) from PID file (%2$s) was not a valid numerical PID!", pidValue, pidFile), e);
+ }
+ finally {
+ IOUtils.close(fileReader);
+ }
+ }
+
+ public static void writePid(final File pidFile, final int pid) throws IOException {
+ Assert.isTrue(pidFile != null && (pidFile.isFile() || pidFile.createNewFile()), String.format(
+ "The file system pathname (%1$s) in which the PID will be written is not a valid file!", pidFile));
+
+ Assert.isTrue(pid > 0, String.format("The PID value (%1$d) must greater than 0!", pid));
+
+ PrintWriter fileWriter = new PrintWriter(new BufferedWriter(new FileWriter(pidFile, false), 16), true);
+
+ try {
+ fileWriter.println(pid);
+ }
+ finally {
+ pidFile.deleteOnExit();
+ IOUtils.close(fileWriter);
+ }
+ }
+
+ protected static class DirectoryPidFileFilter extends PidFileFilter {
+
+ protected static final DirectoryPidFileFilter INSTANCE = new DirectoryPidFileFilter();
+
+ @Override
+ public boolean accept(final File pathname) {
+ return (pathname != null && (pathname.isDirectory() || super.accept(pathname)));
+ }
+ }
+
+ protected static class PidFileFilter implements FileFilter {
+
+ protected static final PidFileFilter INSTANCE = new PidFileFilter();
+
+ @Override
+ public boolean accept(final File pathname) {
+ return (pathname != null && pathname.isFile() && pathname.getName().endsWith(".pid"));
+ }
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/gemfire/test/support/CollectionUtils.java b/src/test/java/org/springframework/data/gemfire/test/support/CollectionUtils.java
new file mode 100644
index 00000000..86bcf4cd
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/test/support/CollectionUtils.java
@@ -0,0 +1,49 @@
+/*
+ * 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.test.support;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+
+/**
+ * The CollectionUtils class is a utility class for working with the Java Collections Framework.
+ *
+ * @author John Blum
+ * @see java.util.Collection
+ * @see java.util.Collections
+ * @since 1.5.0
+ */
+@SuppressWarnings("unused")
+public abstract class CollectionUtils {
+
+ public static Iterable iterable(final Enumeration enumeration) {
+ return new Iterable() {
+ @Override public Iterator iterator() {
+ return org.springframework.util.CollectionUtils.toIterator(enumeration);
+ }
+ };
+ }
+
+ public static Iterable iterable(final Iterator iterator) {
+ return new Iterable() {
+ @Override public Iterator iterator() {
+ return iterator;
+ }
+ };
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/gemfire/test/support/IOUtils.java b/src/test/java/org/springframework/data/gemfire/test/support/IOUtils.java
new file mode 100644
index 00000000..ffef148c
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/test/support/IOUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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.test.support;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The IOUtils class is an utility class working with IO operations.
+ *
+ * @author John Blum
+ * @since 1.5.0
+ */
+@SuppressWarnings("unused")
+public abstract class IOUtils {
+
+ private static final Logger log = Logger.getLogger(IOUtils.class.getName());
+
+ public static boolean close(final Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ return true;
+ }
+ catch (IOException ignore) {
+ if (log.isLoggable(Level.FINE)) {
+ log.fine(String.format("Failed to close Closeable object (%1$s) due to I/O error:%n%2$s",
+ closeable, ThrowableUtils.toString(ignore)));
+ }
+
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/test/java/org/springframework/data/gemfire/test/support/ThreadUtils.java b/src/test/java/org/springframework/data/gemfire/test/support/ThreadUtils.java
new file mode 100644
index 00000000..d2318079
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/test/support/ThreadUtils.java
@@ -0,0 +1,73 @@
+/*
+ * 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.test.support;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The ThreadUtils class is a utility class for working with Java Threads.
+ *
+ * @author John Blum
+ * @see java.lang.Thread
+ * @since 1.5.0
+ */
+@SuppressWarnings("unused")
+public abstract class ThreadUtils {
+
+ public static boolean sleep(final long milliseconds) {
+ try {
+ Thread.sleep(milliseconds);
+ return true;
+ }
+ catch (InterruptedException ignore) {
+ return false;
+ }
+ }
+
+ public static void timedWait(final long milliseconds) {
+ timedWait(milliseconds, milliseconds);
+ }
+
+ public static void timedWait(final long milliseconds, final long interval) {
+ timedWait(milliseconds, interval, new WaitCondition() {
+ @Override public boolean waiting() {
+ return true;
+ }
+ });
+ }
+
+ public static void timedWait(final long milliseconds, long interval, final WaitCondition waitCondition) {
+ final long timeout = (System.currentTimeMillis() + milliseconds);
+
+ interval = Math.min(interval, milliseconds);
+
+ while (waitCondition.waiting() && (System.currentTimeMillis() < timeout)) {
+ try {
+ synchronized (waitCondition) {
+ TimeUnit.MILLISECONDS.timedWait(waitCondition, interval);
+ }
+ }
+ catch (InterruptedException ignore) {
+ }
+ }
+ }
+
+ public static interface WaitCondition {
+ boolean waiting();
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/gemfire/test/support/ThrowableUtils.java b/src/test/java/org/springframework/data/gemfire/test/support/ThrowableUtils.java
new file mode 100644
index 00000000..2af44cd8
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/test/support/ThrowableUtils.java
@@ -0,0 +1,40 @@
+/*
+ * 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.test.support;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * The ExceptionUtils class is a utility class for working with Throwable, Exception and Error objects.
+ *
+ * @author John Blum
+ * @see java.lang.Error
+ * @see java.lang.Exception
+ * @see java.lang.Throwable
+ * @since 1.5.0
+ */
+@SuppressWarnings("unused")
+public abstract class ThrowableUtils {
+
+ public static String toString(Throwable t) {
+ StringWriter writer = new StringWriter();
+ t.printStackTrace(new PrintWriter(writer));
+ return writer.toString();
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/gemfire/test/support/ZipUtils.java b/src/test/java/org/springframework/data/gemfire/test/support/ZipUtils.java
new file mode 100644
index 00000000..f0d01b67
--- /dev/null
+++ b/src/test/java/org/springframework/data/gemfire/test/support/ZipUtils.java
@@ -0,0 +1,70 @@
+/*
+ * 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.test.support;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+import org.springframework.util.FileCopyUtils;
+
+/**
+ * The ZipUtils class is an abstract utility class for working with JAR and ZIP archives.
+ *
+ * @author John Blum
+ * @see java.io.File
+ * @see java.util.zip.ZipFile
+ * @since 1.5.0
+ */
+public abstract class ZipUtils {
+
+ public static void unzip(final Resource zipResource, final File directory) throws IOException {
+ Assert.notNull(zipResource, "The ZIP Resource must not be null!");
+
+ Assert.isTrue(directory != null && directory.isDirectory(), String.format(
+ "The file system pathname (%1$s) is not a valid directory!", directory));
+
+ ZipFile zipFile = new ZipFile(zipResource.getFile(), ZipFile.OPEN_READ);
+
+ for (ZipEntry entry : CollectionUtils.iterable(zipFile.entries())) {
+ if (entry.isDirectory()) {
+ new File(directory, entry.getName()).mkdirs();
+ }
+ else {
+ DataInputStream entryInputStream = new DataInputStream(zipFile.getInputStream(entry));
+
+ DataOutputStream entryOutputStream = new DataOutputStream(new FileOutputStream(
+ new File(directory, entry.getName())));
+
+ try {
+ FileCopyUtils.copy(entryInputStream, entryOutputStream);
+ }
+ finally {
+ IOUtils.close(entryInputStream);
+ IOUtils.close(entryOutputStream);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/test/resources/org/springframework/data/gemfire/cacheUsingSharedConfigurationIntegrationTest.xml b/src/test/resources/org/springframework/data/gemfire/cacheUsingSharedConfigurationIntegrationTest.xml
new file mode 100644
index 00000000..eb9fcea8
--- /dev/null
+++ b/src/test/resources/org/springframework/data/gemfire/cacheUsingSharedConfigurationIntegrationTest.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ CacheUsingSharedConfigurationIntegrationTest
+ 0
+ config
+ localhost[20668]
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/resources/shared_config.zip b/src/test/resources/shared_config.zip
new file mode 100644
index 00000000..e81a520d
Binary files /dev/null and b/src/test/resources/shared_config.zip differ
diff --git a/src/test/resources/sharedconfig-test-cache.xml b/src/test/resources/sharedconfig-test-cache.xml
new file mode 100644
index 00000000..56c87472
--- /dev/null
+++ b/src/test/resources/sharedconfig-test-cache.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ java.lang.Integer
+ java.lang.String
+
+
+
+
+ java.lang.Long
+ java.lang.String
+
+
+
+
+ java.lang.Integer
+ java.lang.String
+
+
+