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 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 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 + + +