diff --git a/src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java b/src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java index c0403123..64c3c1bd 100644 --- a/src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java @@ -16,19 +16,26 @@ package org.springframework.data.gemfire; +import static java.util.stream.StreamSupport.stream; import static org.springframework.data.gemfire.GemfireUtils.apacheGeodeProductName; import static org.springframework.data.gemfire.GemfireUtils.apacheGeodeVersion; import static org.springframework.data.gemfire.support.GemfireBeanFactoryLocator.newBeanFactoryLocator; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeList; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException; import java.io.File; import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.geode.GemFireCheckedException; import org.apache.geode.GemFireException; import org.apache.geode.cache.Cache; @@ -36,20 +43,16 @@ import org.apache.geode.cache.CacheClosedException; import org.apache.geode.cache.CacheFactory; import org.apache.geode.cache.DynamicRegionFactory; import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.RegionService; import org.apache.geode.cache.TransactionListener; import org.apache.geode.cache.TransactionWriter; import org.apache.geode.cache.util.GatewayConflictResolver; -import org.apache.geode.distributed.DistributedMember; import org.apache.geode.distributed.DistributedSystem; import org.apache.geode.internal.datasource.ConfigProperty; import org.apache.geode.internal.jndi.JNDIInvoker; import org.apache.geode.pdx.PdxSerializable; import org.apache.geode.pdx.PdxSerializer; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; @@ -57,51 +60,56 @@ import org.springframework.context.Phased; import org.springframework.core.io.Resource; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer; +import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport; import org.springframework.data.gemfire.support.GemfireBeanFactoryLocator; -import org.springframework.data.gemfire.util.CollectionUtils; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** - * 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} - * 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 appropriately. + * Spring {@link FactoryBean} used to construct, configure and initialize a Pivotal GemFire/Apache Geode + * {@link Cache peer cache). + * + * Allows either retrieval of an existing, opened {@link Cache} or creation of a new {@link Cache}. + * + * This class implements the {@link PersistenceExceptionTranslator} interface and is auto-detected by Spring's + * {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor} for AOP-based translation + * of native persistent store exceptions to Spring's {@link DataAccessException} hierarchy. Therefore, the presence + * of this class automatically enables a {@link org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor} + * to translate Pivotal GemFire/Apache Geode 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.context.Phased - * @see org.springframework.dao.support.PersistenceExceptionTranslator + * @see java.util.Properties * @see org.apache.geode.cache.Cache * @see org.apache.geode.cache.CacheFactory * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.DynamicRegionFactory + * @see org.apache.geode.cache.RegionService * @see org.apache.geode.distributed.DistributedMember * @see org.apache.geode.distributed.DistributedSystem + * @see org.apache.geode.cache.pdx.PdxSerializer + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.beans.factory.InitializingBean + * @see org.springframework.context.Phased + * @see org.springframework.core.io.Resource + * @see org.springframework.dao.support.PersistenceExceptionTranslator + * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer + * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport + * @see org.springframework.data.gemfire.support.GemfireBeanFactoryLocator */ @SuppressWarnings("unused") -public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, BeanNameAware, FactoryBean, - Phased, InitializingBean, DisposableBean, PersistenceExceptionTranslator { +public class CacheFactoryBean extends AbstractFactoryBeanSupport + implements DisposableBean, InitializingBean, PersistenceExceptionTranslator, Phased { private boolean close = true; private boolean useBeanFactoryLocator = false; private int phase = -1; - protected final Log log = LogFactory.getLog(getClass()); - - private BeanFactory beanFactory; - private Boolean copyOnRead; private Boolean enableAutoReconnect; private Boolean pdxIgnoreUnreadFields; @@ -111,13 +119,13 @@ public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, private Cache cache; - private ClassLoader beanClassLoader; - private DynamicRegionSupport dynamicRegionSupport; private Float criticalHeapPercentage; private Float evictionHeapPercentage; + private GatewayConflictResolver gatewayConflictResolver; + protected GemfireBeanFactoryLocator beanFactoryLocator; private Integer lockLease; @@ -125,27 +133,34 @@ public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, private Integer messageSyncInterval; private Integer searchTimeout; + private List peerCacheConfigurers = Collections.emptyList(); + private List jndiDataSources; private List transactionListeners; - // Declared with type 'Object' for backward compatibility - private Object gatewayConflictResolver; - private Object pdxSerializer; + private PdxSerializer pdxSerializer; + + private PeerCacheConfigurer compositePeerCacheConfigurer = (beanName, bean) -> + nullSafeList(peerCacheConfigurers).forEach(peerCacheConfigurer -> + peerCacheConfigurer.configure(beanName, bean)); private Properties properties; private Resource cacheXml; - private String beanName; private String cacheResolutionMessagePrefix; private String pdxDiskStoreName; private TransactionWriter transactionWriter; /** - * @inheritDoc + * Initializes this {@link CacheFactoryBean} after properties have been set by the Spring container. + * + * @throws Exception if initialization fails. * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + * @see #initBeanFactoryLocator() + * @see #postProcessBeforeCacheInitialization(Properties) */ @Override public void afterPropertiesSet() throws Exception { @@ -153,66 +168,111 @@ public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, postProcessBeforeCacheInitialization(resolveProperties()); } - /* (non-Javadoc) */ + /** + * Initializes the {@link GemfireBeanFactoryLocator} if {@link #isUseBeanFactoryLocator()} returns {@literal true} + * and an existing {@link #getBeanFactoryLocator()} is not already present. + * + * @see org.springframework.data.gemfire.support.GemfireBeanFactoryLocator#newBeanFactoryLocator(BeanFactory, String) + * @see #isUseBeanFactoryLocator() + * @see #getBeanFactoryLocator() + * @see #getBeanFactory() + * @see #getBeanName() + */ private void initBeanFactoryLocator() { - if (useBeanFactoryLocator && beanFactoryLocator == null) { - beanFactoryLocator = newBeanFactoryLocator(this.beanFactory, this.beanName); + if (isUseBeanFactoryLocator() && getBeanFactoryLocator() == null) { + this.beanFactoryLocator = newBeanFactoryLocator(getBeanFactory(), getBeanName()); } } - /* (non-Javadoc) */ + /** + * Post processes this {@link CacheFactoryBean} before cache initialization. + * + * This is also the point at which any configured {@link PeerCacheConfigurer} beans are called. + * + * @param gemfireProperties {@link Properties} used to configure Pivotal GemFire/Apache Geode. + * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer + * @see java.util.Properties + */ protected void postProcessBeforeCacheInitialization(Properties gemfireProperties) { + gemfireProperties.setProperty("disable-auto-reconnect", String.valueOf( !Boolean.TRUE.equals(getEnableAutoReconnect()))); + gemfireProperties.setProperty("use-cluster-configuration", String.valueOf( Boolean.TRUE.equals(getUseClusterConfiguration()))); + + applyPeerCacheConfigurers(); } /* (non-Javadoc) */ - protected void setCache(Cache cache) { - this.cache = cache; + private void applyPeerCacheConfigurers() { + applyPeerCacheConfigurers(getCompositePeerCacheConfigurer()); } - /* (non-Javadoc) */ - @SuppressWarnings("unchecked") - protected T getCache() { - return (T) cache; - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.FactoryBean#getObject() + /** + * Null-safe operation to apply the given array of {@link PeerCacheConfigurer PeerCacheConfigurers} + * to this {@link CacheFactoryBean}. + * + * @param peerCacheConfigurers array of {@link PeerCacheConfigurer PeerCacheConfigurers} applied to + * this {@link CacheFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer + * @see #applyPeerCacheConfigurers(Iterable) */ - @Override - public Cache getObject() throws Exception { - return (cache != null ? cache : init()); + protected void applyPeerCacheConfigurers(PeerCacheConfigurer... peerCacheConfigurers) { + applyPeerCacheConfigurers(Arrays.asList(nullSafeArray(peerCacheConfigurers, PeerCacheConfigurer.class))); } - /* (non-Javadoc) */ - Cache init() throws Exception { + /** + * Null-safe operation to apply the given {@link Iterable} of {@link PeerCacheConfigurer PeerCacheConfigurers} + * to this {@link CacheFactoryBean}. + * + * @param peerCacheConfigurers {@link Iterable} of {@link PeerCacheConfigurer PeerCacheConfigurers} + * applied to this {@link CacheFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer + * @see java.lang.Iterable + */ + protected void applyPeerCacheConfigurers(Iterable peerCacheConfigurers) { + stream(nullSafeIterable(peerCacheConfigurers).spliterator(), false) + .forEach(clientCacheConfigurer -> clientCacheConfigurer.configure(getBeanName(), this)); + } + + /** + * Initializes the {@link Cache}. + * + * @return a reference to the initialized {@link Cache}. + * @see org.apache.geode.cache.Cache + * @see #resolveCache() + * @see #postProcess(GemFireCache) + * @see #setCache(Cache) + */ + @SuppressWarnings("deprecation") + Cache init() { + ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader(); try { - // use bean ClassLoader to load Spring configured, GemFire Declarable classes - Thread.currentThread().setContextClassLoader(beanClassLoader); + // use bean ClassLoader to load Spring configured, Pivotal GemFire/Apache Geode classes + Thread.currentThread().setContextClassLoader(getBeanClassLoader()); - cache = postProcess(resolveCache()); + setCache(postProcess(resolveCache())); - DistributedSystem system = cache.getDistributedSystem(); + Optional.ofNullable(this.getCache()).ifPresent(cache -> { + Optional.ofNullable(cache.getDistributedSystem()).map(DistributedSystem::getDistributedMember) + .ifPresent(member -> + logInfo(() -> String.format("Connected to Distributed System [%1$s] as Member [%2$s]" + .concat(" in Group(s) [%3$s] with Role(s) [%4$s] on Host [%5$s] having PID [%6$d]"), + cache.getDistributedSystem().getName(), member.getId(), member.getGroups(), + member.getRoles(), member.getHost(), member.getProcessId()))); - DistributedMember member = system.getDistributedMember(); + logInfo(() -> String.format("%1$s %2$s version [%3$s] Cache [%4$s]", this.cacheResolutionMessagePrefix, + apacheGeodeProductName(), apacheGeodeVersion(), cache.getName())); + }); - if (log.isInfoEnabled()) { - log.info(String.format("Connected to Distributed System [%1$s] as Member [%2$s]" - .concat(" in Group(s) [%3$s] with Role(s) [%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 %2$s version [%3$s] Cache [%4$s].", - cacheResolutionMessagePrefix, apacheGeodeProductName(), apacheGeodeVersion(), cache.getName())); - } - - return cache; + return getCache(); + } + catch (Exception e) { + throw newRuntimeException(e, "Error occurred when initializing peer cache"); } finally { Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader); @@ -220,189 +280,167 @@ public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, } /** - * If Dynamic Regions are enabled, create and initialize a DynamicRegionFactory before creating the Cache. - */ - private void initDynamicRegionFactory() { - if (dynamicRegionSupport != null) { - dynamicRegionSupport.initializeDynamicRegionFactory(); - } - } - - /** - * Resolves the GemFire Cache by first attempting to lookup and find an existing Cache instance in the VM; - * if an existing Cache could not be found, then this method proceeds in attempting to create a new Cache instance. + * Resolves the {@link Cache} by first attempting to lookup an existing {@link Cache} instance in the JVM. + * If an existing {@link Cache} could not be found, then this method proceeds in attempting to create + * a new {@link Cache} instance. * - * @return the resolved GemFire Cache instance. + * @return the resolved {@link Cache} instance. * @see org.apache.geode.cache.Cache * @see #fetchCache() + * @see #resolveProperties() * @see #createFactory(java.util.Properties) * @see #prepareFactory(Object) * @see #createCache(Object) */ protected Cache resolveCache() { try { - cacheResolutionMessagePrefix = "Found existing"; + this.cacheResolutionMessagePrefix = "Found existing"; return (Cache) fetchCache(); } catch (CacheClosedException ex) { - cacheResolutionMessagePrefix = "Created new"; + this.cacheResolutionMessagePrefix = "Created new"; initDynamicRegionFactory(); return (Cache) createCache(prepareFactory(createFactory(resolveProperties()))); } } /** - * Fetches an existing GemFire Cache instance from the CacheFactory. + * Fetches an existing {@link Cache} instance from the {@link CacheFactory}. * - * @param parameterized Class type extension of GemFireCache. - * @return the existing GemFire Cache instance if available. - * @throws org.apache.geode.cache.CacheClosedException if an existing GemFire Cache instance does not exist. - * @see org.apache.geode.cache.GemFireCache + * @param parameterized {@link Class} type extension of {@link GemFireCache}. + * @return an existing {@link Cache} instance if available. + * @throws org.apache.geode.cache.CacheClosedException if an existing {@link Cache} instance does not exist. * @see org.apache.geode.cache.CacheFactory#getAnyInstance() + * @see org.apache.geode.cache.GemFireCache + * @see #getCache() */ @SuppressWarnings("unchecked") protected T fetchCache() { - return (T) (cache != null ? cache : CacheFactory.getAnyInstance()); + return (T) Optional.ofNullable(getCache()).orElseGet(CacheFactory::getAnyInstance); } /** - * Resolves the GemFire System properties used to configure the GemFire Cache instance. + * If Pivotal GemFire/Apache Geode Dynamic Regions are enabled, create and initialize a {@link DynamicRegionFactory} + * before creating the {@link Cache}. * - * @return a Properties object containing GemFire System properties used to configure the GemFire Cache instance. + * @see org.springframework.data.gemfire.CacheFactoryBean.DynamicRegionSupport#initDynamicRegionFactory() + * @see #getDynamicRegionSupport() + */ + private void initDynamicRegionFactory() { + Optional.ofNullable(getDynamicRegionSupport()).ifPresent(DynamicRegionSupport::initializeDynamicRegionFactory); + } + + /** + * Resolves the Pivotal GemFire/Apache Geode {@link Properties} used to configure the {@link Cache}. + * + * @return the resolved Pivotal GemFire/Apache Geode {@link Properties} used to configure the {@link Cache}. + * @see #setAndGetProperties(Properties) * @see #getProperties() */ protected Properties resolveProperties() { - return (properties != null ? properties : (properties = new Properties())); + return Optional.ofNullable(getProperties()).orElseGet(() -> setAndGetProperties(new Properties())); } /** - * Creates an instance of GemFire factory initialized with the given GemFire System Properties - * to create an instance of a GemFire cache. + * Constructs a new instance of {@link CacheFactory} initialized with the given Pivotal GemFire/Apache Geode + * {@link Properties} used to create an instance of a {@link Cache}. * - * @param gemfireProperties a Properties object containing GemFire System properties. - * @return an instance of a GemFire factory used to create a GemFire cache instance. - * @see java.util.Properties + * @param gemfireProperties {@link Properties} used by the {@link CacheFactory} to configure the {@link Cache}. + * @return a new instance of {@link CacheFactory} initialized with the given Pivotal GemFire/Apache Geode + * {@link Properties}. * @see org.apache.geode.cache.CacheFactory + * @see java.util.Properties */ protected Object createFactory(Properties gemfireProperties) { return new CacheFactory(gemfireProperties); } /** - * Initializes the GemFire factory used to create the GemFire cache instance. Sets PDX options - * specified by the user. + * Prepares and initializes the {@link CacheFactory} used to create the {@link Cache}. * - * @param factory the GemFire factory used to create an instance of the GemFire cache. - * @return the initialized GemFire cache factory. - * @see #isPdxOptionsSpecified() + * Sets PDX options specified by the user. + * + * @param factory {@link CacheFactory} used to create the {@link Cache}. + * @return the prepared and initialized {@link CacheFactory}. + * @see #initializePdx(CacheFactory) */ protected Object prepareFactory(Object factory) { return initializePdx((CacheFactory) factory); } /** - * Initialize the PDX settings on the {@link CacheFactory}. + * Configure PDX for the given {@link CacheFactory}. * - * @param cacheFactory the GemFire {@link CacheFactory} used to configure and create a GemFire {@link Cache}. + * @param cacheFactory {@link CacheFactory} used to configure PDX. + * @return the given {@link CacheFactory}. * @see org.apache.geode.cache.CacheFactory */ - CacheFactory initializePdx(CacheFactory cacheFactory) { - if (isPdxOptionsSpecified()) { - if (pdxSerializer != null) { - Assert.isInstanceOf(PdxSerializer.class, pdxSerializer, - String.format("[%1$s] of type [%2$s] is not a PdxSerializer", pdxSerializer, - ObjectUtils.nullSafeClassName(pdxSerializer))); + private CacheFactory initializePdx(CacheFactory cacheFactory) { - cacheFactory.setPdxSerializer((PdxSerializer) pdxSerializer); - } - if (pdxDiskStoreName != null) { - cacheFactory.setPdxDiskStore(pdxDiskStoreName); - } - if (pdxIgnoreUnreadFields != null) { - cacheFactory.setPdxIgnoreUnreadFields(pdxIgnoreUnreadFields); - } - if (pdxPersistent != null) { - cacheFactory.setPdxPersistent(pdxPersistent); - } - if (pdxReadSerialized != null) { - cacheFactory.setPdxReadSerialized(pdxReadSerialized); - } - } + Optional.ofNullable(getPdxSerializer()).ifPresent(cacheFactory::setPdxSerializer); + + Optional.ofNullable(getPdxDiskStoreName()).filter(StringUtils::hasText) + .ifPresent(cacheFactory::setPdxDiskStore); + + Optional.ofNullable(getPdxIgnoreUnreadFields()).ifPresent(cacheFactory::setPdxIgnoreUnreadFields); + + Optional.ofNullable(getPdxPersistent()).ifPresent(cacheFactory::setPdxPersistent); + + Optional.ofNullable(getPdxReadSerialized()).ifPresent(cacheFactory::setPdxReadSerialized); return cacheFactory; } /** - * Determines whether the user specified PDX options. + * Creates a new {@link Cache} instance using the provided factory. * - * @return a boolean value indicating whether the user specified PDX options or not. - */ - protected boolean isPdxOptionsSpecified() { - return (pdxSerializer != null || pdxReadSerialized != null || pdxPersistent != null - || pdxIgnoreUnreadFields != null || pdxDiskStoreName != null); - } - - /** - * Creates a new GemFire cache instance using the provided factory. - * - * @param parameterized Class type extension of GemFireCache. - * @param factory the appropriate GemFire factory used to create a cache instance. - * @return an instance of the GemFire cache. - * @see org.apache.geode.cache.GemFireCache + * @param parameterized {@link Class} type extension of {@link GemFireCache}. + * @param factory instance of {@link CacheFactory}. + * @return a new instance of {@link Cache} created by the provided factory. * @see org.apache.geode.cache.CacheFactory#create() + * @see org.apache.geode.cache.GemFireCache */ @SuppressWarnings("unchecked") protected T createCache(Object factory) { - return (T) (cache != null ? cache : ((CacheFactory) factory).create()); + return (T) Optional.ofNullable(getCache()).orElseGet(((CacheFactory) factory)::create); } /** - * Post processes the GemFire Cache instance by loading any cache.xml, applying settings specified in SDG XML - * configuration meta-data, and registering the appropriate Transaction Listeners, Writer and JNDI settings. + * Post processes the {@link GemFireCache} by loading any {@literal cache.xml}, applying custom settings + * specified in SDG XML configuration meta-data, and registering appropriate Transaction Listeners, Writer + * and JNDI settings. * - * @param parameterized Class type extension of GemFireCache. - * @param cache the GemFire Cache instance to process. - * @return the GemFire Cache instance after processing. - * @throws IOException if the cache.xml Resource could not be loaded and applied to the Cache instance. + * @param Parameterized {@link Class} type extension of {@link GemFireCache}. + * @param cache {@link GemFireCache} instance to post process. + * @return the given {@link GemFireCache}. * @see org.apache.geode.cache.Cache#loadCacheXml(java.io.InputStream) * @see #getCacheXml() - * @see #setHeapPercentages(org.apache.geode.cache.GemFireCache) + * @see #configureHeapPercentages(org.apache.geode.cache.GemFireCache) + * @see #registerJndiDataSources() * @see #registerTransactionListeners(org.apache.geode.cache.GemFireCache) * @see #registerTransactionWriter(org.apache.geode.cache.GemFireCache) - * @see #registerJndiDataSources() */ - protected T postProcess(T cache) throws IOException { - Resource localCacheXml = getCacheXml(); + protected T postProcess(T cache) { - // load cache.xml Resource and initialize the Cache - if (localCacheXml != null) { - if (log.isDebugEnabled()) { - log.debug(String.format("initializing Cache with '%1$s'", cacheXml)); + // load cache.xml Resource and initialize the cache + Optional.ofNullable(getCacheXml()).ifPresent(cacheXml -> { + try { + logDebug("Initializing cache with [%s]", cacheXml); + cache.loadCacheXml(cacheXml.getInputStream()); } + catch (IOException e) { + throw newRuntimeException(e, "Failed to load cache.xml [%s]", cacheXml); + } + }); - cache.loadCacheXml(localCacheXml.getInputStream()); - } + Optional.ofNullable(getCopyOnRead()).ifPresent(cache::setCopyOnRead); + Optional.ofNullable(getGatewayConflictResolver()).ifPresent(((Cache) cache)::setGatewayConflictResolver); + Optional.ofNullable(getLockLease()).ifPresent(((Cache) cache)::setLockLease); + Optional.ofNullable(getLockTimeout()).ifPresent(((Cache) cache)::setLockTimeout); + Optional.ofNullable(getMessageSyncInterval()).ifPresent(((Cache) cache)::setMessageSyncInterval); + Optional.ofNullable(getSearchTimeout()).ifPresent(((Cache) cache)::setSearchTimeout); - if (this.copyOnRead != null) { - cache.setCopyOnRead(this.copyOnRead); - } - if (gatewayConflictResolver != null) { - ((Cache) cache).setGatewayConflictResolver((GatewayConflictResolver) gatewayConflictResolver); - } - if (lockLease != null) { - ((Cache) cache).setLockLease(lockLease); - } - if (lockTimeout != null) { - ((Cache) cache).setLockTimeout(lockTimeout); - } - if (messageSyncInterval != null) { - ((Cache) cache).setMessageSyncInterval(messageSyncInterval); - } - if (searchTimeout != null) { - ((Cache) cache).setSearchTimeout(searchTimeout); - } - - setHeapPercentages(cache); + configureHeapPercentages(cache); registerTransactionListeners(cache); registerTransactionWriter(cache); registerJndiDataSources(); @@ -411,203 +449,174 @@ public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, } /* (non-Javadoc) */ - private void setHeapPercentages(GemFireCache cache) { - if (criticalHeapPercentage != null) { - Assert.isTrue(criticalHeapPercentage > 0.0 && criticalHeapPercentage <= 100.0, - String.format("'criticalHeapPercentage' (%1$s) is invalid; must be > 0.0 and <= 100.0", - criticalHeapPercentage)); + private boolean isHeapPercentageValid(Float heapPercentage) { + return (heapPercentage > 0.0f && heapPercentage <= 100.0f); + } + + /* (non-Javadoc) */ + private void configureHeapPercentages(GemFireCache cache) { + Optional.ofNullable(getCriticalHeapPercentage()).ifPresent(criticalHeapPercentage -> { + Assert.isTrue(isHeapPercentageValid(criticalHeapPercentage), String.format( + "criticalHeapPercentage [%s] is not valid; must be > 0.0 and <= 100.0", criticalHeapPercentage)); + cache.getResourceManager().setCriticalHeapPercentage(criticalHeapPercentage); - } + }); + + Optional.ofNullable(getEvictionHeapPercentage()).ifPresent(evictionHeapPercentage -> { + Assert.isTrue(isHeapPercentageValid(evictionHeapPercentage), String.format( + "evictionHeapPercentage [%s] is not valid; must be > 0.0 and <= 100.0", evictionHeapPercentage)); - if (evictionHeapPercentage != null) { - Assert.isTrue(evictionHeapPercentage > 0.0 && evictionHeapPercentage <= 100.0, - String.format("'evictionHeapPercentage' (%1$s) is invalid; must be > 0.0 and <= 100.0", - evictionHeapPercentage)); cache.getResourceManager().setEvictionHeapPercentage(evictionHeapPercentage); - } - } - - /* (non-Javadoc) */ - private void registerTransactionListeners(GemFireCache cache) { - for (TransactionListener transactionListener : CollectionUtils.nullSafeCollection(transactionListeners)) { - cache.getCacheTransactionManager().addListener(transactionListener); - } - } - - /* (non-Javadoc) */ - private void registerTransactionWriter(GemFireCache cache) { - if (transactionWriter != null) { - cache.getCacheTransactionManager().setWriter(transactionWriter); - } + }); } /* (non-Javadoc) */ private void registerJndiDataSources() { - for (JndiDataSource jndiDataSource : CollectionUtils.nullSafeCollection(jndiDataSources)) { - String typeAttributeValue = jndiDataSource.getAttributes().get("type"); - JndiDataSourceType jndiDataSourceType = JndiDataSourceType.valueOfIgnoreCase(typeAttributeValue); + + nullSafeCollection(getJndiDataSources()).forEach(jndiDataSource -> { + + String type = jndiDataSource.getAttributes().get("type"); + + JndiDataSourceType jndiDataSourceType = JndiDataSourceType.valueOfIgnoreCase(type); Assert.notNull(jndiDataSourceType, String.format( - "'jndi-binding' 'type' [%1$s] is invalid; 'type' must be one of %2$s", typeAttributeValue, - Arrays.toString(JndiDataSourceType.values()))); + "'jndi-binding' 'type' [%1$s] is invalid; 'type' must be one of %2$s", type, + Arrays.toString(JndiDataSourceType.values()))); jndiDataSource.getAttributes().put("type", jndiDataSourceType.getName()); JNDIInvoker.mapDatasource(jndiDataSource.getAttributes(), jndiDataSource.getProps()); - } + }); } + /* (non-Javadoc) */ + private void registerTransactionListeners(GemFireCache cache) { + nullSafeCollection(getTransactionListeners()) + .forEach(transactionListener -> cache.getCacheTransactionManager().addListener(transactionListener)); + } + + /* (non-Javadoc) */ + private void registerTransactionWriter(GemFireCache cache) { + Optional.ofNullable(getTransactionWriter()).ifPresent(it -> cache.getCacheTransactionManager().setWriter(it)); + } + + /** + * Destroys the {@link Cache} bean on Spring container shutdown. + * + * @throws Exception if an error occurs while closing the cache. + * @see org.springframework.beans.factory.DisposableBean#destroy() + * @see #destroyBeanFactoryLocator() + * @see #close(GemFireCache) + * @see #isClose() + */ @Override public void destroy() throws Exception { - if (close) { - Cache localCache = fetchCache(); - - if (localCache != null && !localCache.isClosed()) { - close(localCache); - } - - this.cache = null; - - if (beanFactoryLocator != null) { - beanFactoryLocator.destroy(); - beanFactoryLocator = null; - } + if (isClose()) { + close(fetchCache()); + destroyBeanFactoryLocator(); } } - /* (non-Javadoc) */ + /** + * Null-safe internal method used to close the {@link GemFireCache} and calling {@link GemFireCache#close()} + * iff the cache {@link GemFireCache#isClosed() is not already closed}. + * + * @param cache {@link GemFireCache} to close. + * @see org.apache.geode.cache.GemFireCache#isClosed() + * @see org.apache.geode.cache.GemFireCache#close() + */ protected void close(GemFireCache cache) { - cache.close(); + + Optional.ofNullable(cache) + .filter(it -> !it.isClosed()) + .ifPresent(RegionService::close); + + this.cache = null; } - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.FactoryBean#getObjectType() + /** + * Destroys the {@link GemfireBeanFactoryLocator}. + * + * @see org.springframework.data.gemfire.support.GemfireBeanFactoryLocator#destroy() + */ + private void destroyBeanFactoryLocator() { + Optional.ofNullable(getBeanFactoryLocator()).ifPresent(GemfireBeanFactoryLocator::destroy); + this.beanFactoryLocator = null; + } + + /** + * Translates the given Pivotal GemFire/Apache Geode {@link RuntimeException} thrown to a corresponding exception + * from Spring's generic {@link DataAccessException} hierarchy, if possible. + * + * @param exception {@link RuntimeException} to translate. + * @return the translated Spring {@link DataAccessException} or {@literal null} if the Pivotal GemFire/Apache Geode + * {@link RuntimeException} could not be converted. + * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(RuntimeException) + * @see org.springframework.dao.DataAccessException */ @Override - public Class getObjectType() { - return (cache != null ? cache.getClass() : Cache.class); - } + public DataAccessException translateExceptionIfPossible(RuntimeException exception) { - /* (non-Javadoc) */ - protected void setPhase(int phase) { - this.phase = phase; - } - - /* - * (non-Javadoc) - * @see org.springframework.context.Phased#getPhase() - */ - @Override - public int getPhase() { - return phase; - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.FactoryBean#isSingleton() - */ - @Override - public boolean isSingleton() { - return true; - } - - @Override - public DataAccessException translateExceptionIfPossible(RuntimeException e) { - if (e instanceof GemFireException) { - return GemfireCacheUtils.convertGemfireAccessException((GemFireException) e); - } - - if (e instanceof IllegalArgumentException) { - DataAccessException wrapped = GemfireCacheUtils.convertQueryExceptions(e); - // ignore conversion if the generic exception is returned + if (exception instanceof IllegalArgumentException) { + DataAccessException wrapped = GemfireCacheUtils.convertQueryExceptions(exception); + // ignore conversion if generic exception is returned if (!(wrapped instanceof GemfireSystemException)) { return wrapped; } } - if (e.getCause() instanceof GemFireException) { - return GemfireCacheUtils.convertGemfireAccessException((GemFireException) e.getCause()); + if (exception instanceof GemFireException) { + return GemfireCacheUtils.convertGemfireAccessException((GemFireException) exception); } - if (e.getCause() instanceof GemFireCheckedException) { - return GemfireCacheUtils.convertGemfireAccessException((GemFireCheckedException) e.getCause()); + if (exception.getCause() instanceof GemFireException) { + return GemfireCacheUtils.convertGemfireAccessException((GemFireException) exception.getCause()); + } + + if (exception.getCause() instanceof GemFireCheckedException) { + return GemfireCacheUtils.convertGemfireAccessException((GemFireCheckedException) exception.getCause()); } return null; } /** - * Sets a reference to the {@link ClassLoader} used to load and create bean classes in the Spring container. + * Returns a reference to the configured {@link GemfireBeanFactoryLocator} used to resolve Spring bean references + * in native Pivotal GemFire/Apache Geode native config (e.g. {@literal cache.xml}). * - * @param classLoader the {@link ClassLoader} used to load and create beans in the Spring container. - * @see java.lang.ClassLoader + * @return a reference to the configured {@link GemfireBeanFactoryLocator}. + * @see org.springframework.data.gemfire.support.GemfireBeanFactoryLocator */ - @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.beanClassLoader = classLoader; - } - - /** - * Gets a reference to the {@link ClassLoader} used to load and create bean classes in the Spring container. - * - * @return the {@link ClassLoader} used to load and create beans in the Spring container. - * @see java.lang.ClassLoader - */ - public ClassLoader getBeanClassLoader() { - return beanClassLoader; - } - - /** - * Sets a reference to the Spring {@link BeanFactory} containing this GemFire {@link Cache} {@link FactoryBean}. - * - * @param beanFactory a reference to the Spring {@link BeanFactory}. - * @see org.springframework.beans.factory.BeanFactory - * @see #getBeanFactory() - */ - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - /** - * Gets a reference to the Spring BeanFactory containing this GemFire {@link Cache} {@link FactoryBean}. - * - * @return a reference to the Spring {@link BeanFactory}. - * @see org.springframework.beans.factory.BeanFactory - * @see #setBeanFactory(BeanFactory) - */ - public BeanFactory getBeanFactory() { - return beanFactory; - } - - /* (non-Javadoc) */ public GemfireBeanFactoryLocator getBeanFactoryLocator() { - return beanFactoryLocator; + return this.beanFactoryLocator; } /** - * Sets the Spring bean name for this GemFire {@link Cache}. + * Sets a reference to the constructed, configured an initialized {@link Cache} + * created by this {@link CacheFactoryBean}. * - * @param name a String value indicating the Spring container bean name for the GemFire {@link Cache} object. + * @param cache {@link Cache} created by this {@link CacheFactoryBean}. + * @see org.apache.geode.cache.Cache */ - @Override - public void setBeanName(String name) { - this.beanName = name; + protected void setCache(Cache cache) { + this.cache = cache; } /** - * Gets the Spring bean name for this GemFire {@link Cache}. + * Returns a direct reference to the constructed, configured an initialized {@link Cache} + * created by this {@link CacheFactoryBean}. * - * @return a String value indicating the Spring container bean name for the GemFire {@link Cache} object. + * @return a direct reference to the {@link Cache} created by this {@link CacheFactoryBean}. + * @see org.apache.geode.cache.Cache */ - public String getBeanName() { - return beanName; + @SuppressWarnings("unchecked") + protected T getCache() { + return (T) this.cache; } /** - * Sets the {@link Cache} configuration meta-data. + * Sets a reference to the Pivotal GemFire/Apache Geode native {@literal cache.xml} {@link Resource}. * - * @param cacheXml the cache.xml {@link Resource} used to initialize the GemFire {@link Cache}. + * @param cacheXml reference to the Pivotal GemFire/Apache Geode native {@literal cache.xml} {@link Resource}. * @see org.springframework.core.io.Resource */ public void setCacheXml(Resource cacheXml) { @@ -615,69 +624,160 @@ public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, } /** - * Gets a reference to the GemFire native cache.xml file as a Spring {@link Resource}. + * Returns a reference to the Pivotal GemFire/Apache Geode native {@literal cache.xml} + * as a Spring {@link Resource}. * - * @return the a reference to the GemFire native cache.xml as a Spring {@link Resource}. + * @return a reference to the Pivotal GemFire/Apache Geode native {@literal cache.xml} + * as a Spring {@link Resource}. * @see org.springframework.core.io.Resource */ public Resource getCacheXml() { - return cacheXml; + return this.cacheXml; } - /* (non-Javadoc) */ + /** + * Returns the {@literal cache.xml} {@link Resource} as a {@link File}. + * + * @return the {@literal cache.xml} {@link Resource} as a {@link File}. + * @throws IllegalStateException if the {@link Resource} is not a valid {@link File} in the file system + * or a general problem exists accessing or reading the {@link File}. + * @see org.springframework.core.io.Resource + * @see java.io.File + * @see #getCacheXml() + */ private File getCacheXmlFile() { try { return getCacheXml().getFile(); } - catch (IOException e) { - throw new IllegalStateException(String.format("Resource (%1$s) is not resolvable as a file", e)); + catch (Throwable e) { + throw newIllegalStateException(e, "Resource [%s] is not resolvable as a file", getCacheXml()); } } - /* (non-Javadoc) */ + /** + * Determines whether the {@link Resource cache.xml} {@link File} is present. + * + * @return boolean value indicating whether a {@link Resource cache.xml} {@link File} is present. + * @see #getCacheXmlFile() + */ private boolean isCacheXmlAvailable() { try { - Resource localCacheXml = getCacheXml(); - return (localCacheXml != null && localCacheXml.getFile().isFile()); + return (getCacheXmlFile() != null); } - catch (IOException ignore) { + catch (Throwable ignore) { return false; } } /** - * Sets the cache properties. + * Returns an object reference to the {@link Cache} created by this {@link CacheFactoryBean}. * - * @param properties the properties to set + * @return an object reference to the {@link Cache} created by this {@link CacheFactoryBean}. + * @see org.springframework.beans.factory.FactoryBean#getObject() + * @see org.apache.geode.cache.Cache + * @see #getCache() + */ + @Override + public Cache getObject() throws Exception { + return Optional.ofNullable(this.getCache()).orElseGet(this::init); + } + + /** + * Returns the {@link Class} type of the {@link GemFireCache} produced by this {@link CacheFactoryBean}. + * + * @return the {@link Class} type of the {@link GemFireCache} produced by this {@link CacheFactoryBean}. + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + @Override + @SuppressWarnings("unchecked") + public Class getObjectType() { + return Optional.ofNullable(getCache()).map(Object::getClass).orElse(Cache.class); + } + + /** + * Set the phase for the {@link Cache} bean in the lifecycle managed by the Spring container. + * + * @param phase {@link Integer#TYPE int} value indicating the phase of this {@link Cache} bean + * in the lifecycle managed by the Spring container. + * @see org.springframework.context.Phased#getPhase() + */ + protected void setPhase(int phase) { + this.phase = phase; + } + + /** + * Returns the configured phase of the {@link Cache} bean in the lifecycle managed by the Spring container. + * + * @return an {@link Integer#TYPE int} value indicating the phase of this {@link Cache} bean in the lifecycle + * managed by the Spring container. + * @see org.springframework.context.Phased#getPhase() + */ + @Override + public int getPhase() { + return this.phase; + } + + /** + * Sets and then returns a reference to Pivotal GemFire/Apache Geode {@link Properties} used to configure the cache. + * + * @param properties reference to Pivotal GemFire/Apache Geode {@link Properties} used to configure the cache. + * @return a reference to Pivotal GemFire/Apache Geode {@link Properties} used to configure the cache. + * @see java.util.Properties + * @see #setProperties(Properties) + * @see #getProperties() + */ + protected Properties setAndGetProperties(Properties properties) { + setProperties(properties); + return getProperties(); + } + + /** + * Returns a reference to Pivotal GemFire/Apache Geode {@link Properties} used to configure the cache. + * + * @param properties reference to Pivotal GemFire/Apache Geode {@link Properties} used to configure the cache. + * @see java.util.Properties */ public void setProperties(Properties properties) { this.properties = properties; } /** - * Gets a reference to the GemFire System Properties. + * Returns a reference to Pivotal GemFire/Apache Geode {@link Properties} used to configure the cache. * - * @return a reference to the GemFire System Properties. + * @return a reference to Pivotal GemFire/Apache Geode {@link Properties}. * @see java.util.Properties */ public Properties getProperties() { - return properties; + return this.properties; } /** - * Set whether the Cache should be closed. + * Sets a value to indicate whether the cache will be closed on shutdown of the Spring container. * - * @param close set to false if destroy() should not close the cache + * @param close boolean value indicating whether the cache will be closed on shutdown of the Spring container. */ public void setClose(boolean close) { this.close = close; } /** - * @return close. + * Returns a boolean value indicating whether the cache will be closed on shutdown of the Spring container. + * + * @return a boolean value indicating whether the cache will be closed on shutdown of the Spring container. */ - public Boolean getClose() { - return close; + public boolean isClose() { + return this.close; + } + + /** + * Returns a reference to the Composite {@link PeerCacheConfigurer} used to apply additional configuration + * to this {@link CacheFactoryBean} on Spring container initialization. + * + * @return the Composite {@link PeerCacheConfigurer}. + * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer + */ + public PeerCacheConfigurer getCompositePeerCacheConfigurer() { + return this.compositePeerCacheConfigurer; } /** @@ -770,14 +870,14 @@ public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, * compatibility with Gemfire 6 compatibility. This must be an instance of * {@link org.apache.geode.cache.util.GatewayConflictResolver} */ - public void setGatewayConflictResolver(Object gatewayConflictResolver) { + public void setGatewayConflictResolver(GatewayConflictResolver gatewayConflictResolver) { this.gatewayConflictResolver = gatewayConflictResolver; } /** * @return the gatewayConflictResolver */ - public Object getGatewayConflictResolver() { + public GatewayConflictResolver getGatewayConflictResolver() { return gatewayConflictResolver; } @@ -920,17 +1020,42 @@ public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, * * @param serializer pdx serializer configured for this cache. */ - public void setPdxSerializer(Object serializer) { + public void setPdxSerializer(PdxSerializer serializer) { this.pdxSerializer = serializer; } /** * @return the pdxSerializer */ - public Object getPdxSerializer() { + public PdxSerializer getPdxSerializer() { return pdxSerializer; } + /** + * Null-safe operation to set an array of {@link PeerCacheConfigurer PeerCacheConfigurers} used to apply + * additional configuration to this {@link CacheFactoryBean} when using Annotation-based configuration. + * + * @param peerCacheConfigurers array of {@link PeerCacheConfigurer PeerCacheConfigurers} used to apply + * additional configuration to this {@link CacheFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer + * @see #setPeerCacheConfigurers(List) + */ + public void setPeerCacheConfigurers(PeerCacheConfigurer... peerCacheConfigurers) { + setPeerCacheConfigurers(Arrays.asList(nullSafeArray(peerCacheConfigurers, PeerCacheConfigurer.class))); + } + + /** + * Null-safe operation to set an {@link Iterable} of {@link PeerCacheConfigurer PeerCacheConfigurers} to apply + * additional configuration to this {@link CacheFactoryBean} when using Annotation-based configuration. + * + * @param peerCacheConfigurers {@link Iterable} of {@link PeerCacheConfigurer PeerCacheConfigurers} used to apply + * additional configuration to this {@link CacheFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer + */ + public void setPeerCacheConfigurers(List peerCacheConfigurers) { + this.peerCacheConfigurers = Optional.ofNullable(peerCacheConfigurers).orElseGet(Collections::emptyList); + } + /** * Set the number of seconds a netSearch operation can wait for data before timing out. * @@ -984,42 +1109,48 @@ public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, } /** - * Indicates whether a bean factory locator is enabled for this cache definition or not. The locator stores - * the enclosing bean factory reference to allow auto-wiring of Spring beans into GemFire managed classes. - * Usually disabled when the same cache is used in multiple application context/bean factories inside - * the same VM. + * Sets whether to enable the {@link GemfireBeanFactoryLocator}. * - * @param usage true if the bean factory locator is to be used underneath the hood. + * @param use boolean value indicating whether to enable the {@link GemfireBeanFactoryLocator}. + * @see org.springframework.data.gemfire.support.GemfireBeanFactoryLocator */ - public void setUseBeanFactoryLocator(boolean usage) { - this.useBeanFactoryLocator = usage; + public void setUseBeanFactoryLocator(boolean use) { + this.useBeanFactoryLocator = use; } /** - * @return useBeanFactoryLocator + * Determines whether the {@link GemfireBeanFactoryLocator} has been enabled. + * + * @return a boolean value indicating whether the {@link GemfireBeanFactoryLocator} has been enabled. + * @see org.springframework.data.gemfire.support.GemfireBeanFactoryLocator */ public boolean isUseBeanFactoryLocator() { - return useBeanFactoryLocator; + return this.useBeanFactoryLocator; } /** - * Sets the state of the use-shared-configuration GemFire distribution config setting. + * Sets the state of the {@literal use-shared-configuration} Pivotal GemFire/Apache Geode + * distribution configuration setting. * - * @param useSharedConfiguration a boolean value to set the use-shared-configuration GemFire distribution property. + * @param useSharedConfiguration boolean value to set the {@literal use-shared-configuration} + * Pivotal GemFire/Apache Geode distribution configuration setting. */ public void setUseClusterConfiguration(Boolean useSharedConfiguration) { this.useClusterConfiguration = useSharedConfiguration; } /** - * Gets the value of the use-shared-configuration GemFire configuration setting. + * Return the state of the {@literal use-shared-configuration} Pivotal GemFire/Apache Geode + * distribution configuration setting. * - * @return a boolean value indicating whether shared configuration use has been enabled or not. + * @return the current boolean value for the {@literal use-shared-configuration} + * Pivotal GemFire/Apache Geode distribution configuration setting. */ public Boolean getUseClusterConfiguration() { return this.useClusterConfiguration; } + /* (non-Javadoc) */ public static class DynamicRegionSupport { private Boolean persistent = Boolean.TRUE; @@ -1070,6 +1201,7 @@ public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, } } + /* (non-Javadoc) */ public static class JndiDataSource { private List props; @@ -1091,5 +1223,4 @@ public class CacheFactoryBean implements BeanClassLoaderAware, BeanFactoryAware, this.props = props; } } - } diff --git a/src/main/java/org/springframework/data/gemfire/DiskStoreFactoryBean.java b/src/main/java/org/springframework/data/gemfire/DiskStoreFactoryBean.java index b752db35..b85f07f7 100644 --- a/src/main/java/org/springframework/data/gemfire/DiskStoreFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/DiskStoreFactoryBean.java @@ -16,30 +16,44 @@ package org.springframework.data.gemfire; +import static java.util.stream.StreamSupport.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; + import java.io.File; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Optional; import org.apache.geode.cache.DiskStore; import org.apache.geode.cache.DiskStoreFactory; import org.apache.geode.cache.GemFireCache; -import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.gemfire.config.annotation.DiskStoreConfigurer; +import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** - * FactoryBean for creating a GemFire DiskStore. + * Spring {@link FactoryBean} used to create {@link DiskStore}. * * @author David Turanski * @author John Blum - * @see org.springframework.beans.factory.BeanNameAware + * @see org.apache.geode.cache.DiskStore + * @see org.apache.geode.cache.DiskStoreFactory + * @see org.apache.geode.cache.GemFireCache * @see org.springframework.beans.factory.FactoryBean * @see org.springframework.beans.factory.InitializingBean + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfigurer + * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport */ @SuppressWarnings("unused") -public class DiskStoreFactoryBean implements BeanNameAware , FactoryBean, InitializingBean { +public class DiskStoreFactoryBean extends AbstractFactoryBeanSupport implements InitializingBean { private Boolean allowForceCompaction; private Boolean autoCompact; @@ -57,78 +71,184 @@ public class DiskStoreFactoryBean implements BeanNameAware , FactoryBean diskStoreConfigurers = Collections.emptyList(); + + private DiskStoreConfigurer compositeDiskStoreConfigurer = (beanName, bean) -> + nullSafeCollection(diskStoreConfigurers).forEach(diskStoreConfigurer -> + diskStoreConfigurer.configure(beanName, bean)); + private List diskDirs; - private String name; - - @Override - public DiskStore getObject() throws Exception { - return diskStore; - } - - @Override - public Class getObjectType() { - return (diskStore != null ? diskStore.getClass() : DiskStore.class); - } - - @Override - public boolean isSingleton() { - return true; - } - @Override public void afterPropertiesSet() throws Exception { - Assert.state(cache != null, String.format("A reference to the GemFire Cache must be set for Disk Store '%1$s'.", - getName())); - DiskStoreFactory diskStoreFactory = cache.createDiskStoreFactory(); + String diskStoreName = resolveDiskStoreName(); - if (allowForceCompaction != null) { - diskStoreFactory.setAllowForceCompaction(allowForceCompaction); - } - if (autoCompact != null) { - diskStoreFactory.setAutoCompact(autoCompact); - } - if (compactionThreshold != null) { - diskStoreFactory.setCompactionThreshold(compactionThreshold); - } - if (diskUsageCriticalPercentage != null) { - diskStoreFactory.setDiskUsageCriticalPercentage(diskUsageCriticalPercentage); - } - if (diskUsageWarningPercentage != null) { - diskStoreFactory.setDiskUsageWarningPercentage(diskUsageWarningPercentage); - } - if (maxOplogSize != null) { - diskStoreFactory.setMaxOplogSize(maxOplogSize); - } - if (queueSize != null) { - diskStoreFactory.setQueueSize(queueSize); - } - if (timeInterval != null) { - diskStoreFactory.setTimeInterval(timeInterval); - } - if (writeBufferSize != null) { - diskStoreFactory.setWriteBufferSize(writeBufferSize); - } + applyDiskStoreConfigurers(diskStoreName); - if (!CollectionUtils.isEmpty(diskDirs)) { - File[] diskDirFiles = new File[diskDirs.size()]; - int[] diskDirSizes = new int[diskDirs.size()]; + GemFireCache cache = resolveCache(diskStoreName); - for (int index = 0; index < diskDirs.size(); index++) { - DiskDir diskDir = diskDirs.get(index); - diskDirFiles[index] = new File(diskDir.location); - diskDirSizes[index] = (diskDir.maxSize != null ? diskDir.maxSize - : DiskStoreFactory.DEFAULT_DISK_DIR_SIZE); - } + DiskStoreFactory diskStoreFactory = postProcess(configure(createDiskStoreFactory(cache))); - diskStoreFactory.setDiskDirsAndSizes(diskDirFiles, diskDirSizes); - } + this.diskStore = postProcess(newDiskStore(diskStoreFactory, diskStoreName)); + } - diskStore = diskStoreFactory.create(getName()); + /* (non-Javadoc) */ + private void applyDiskStoreConfigurers(String diskStoreName) { + applyDiskStoreConfigurers(diskStoreName, getCompositeDiskStoreConfigurer()); + } - Assert.notNull(diskStore, String.format("DiskStore with name '%1$s' failed to be created successfully.", - diskStore.getName())); + /** + * Null-safe operation to apply the given array of {@link DiskStoreConfigurer DiskStoreConfigurers} + * to this {@link DiskStoreFactoryBean}. + * + * @param diskStoreName {@link String} containing the name of the {@link DiskStore}. + * @param diskStoreConfigurers array of {@link DiskStoreConfigurer DiskStoreConfigurers} applied + * to this {@link DiskStoreFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfigurer + * @see #applyDiskStoreConfigurers(String, Iterable) + */ + protected void applyDiskStoreConfigurers(String diskStoreName, DiskStoreConfigurer... diskStoreConfigurers) { + applyDiskStoreConfigurers(diskStoreName, + Arrays.asList(nullSafeArray(diskStoreConfigurers, DiskStoreConfigurer.class))); + } + + /** + * Null-safe operation to apply the given {@link Iterable} of {@link DiskStoreConfigurer DiskStoreConfigurers} + * to this {@link DiskStoreFactoryBean}. + * + * @param diskStoreName {@link String} containing the name of the {@link DiskStore}. + * @param diskStoreConfigurers {@link Iterable} of {@link DiskStoreConfigurer DiskStoreConfigurers} applied + * to this {@link DiskStoreFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfigurer + */ + protected void applyDiskStoreConfigurers(String diskStoreName, Iterable diskStoreConfigurers) { + stream(nullSafeIterable(diskStoreConfigurers).spliterator(), false) + .forEach(diskStoreConfigurer -> diskStoreConfigurer.configure(diskStoreName, this)); + } + + /* (non-Javadoc) */ + private GemFireCache resolveCache(String diskStoreName) { + return Optional.ofNullable(this.cache) + .orElseThrow(() -> newIllegalStateException("Cache is required to create DiskStore [%s]", diskStoreName)); + } + + /* (non-Javadoc) */ + final String resolveDiskStoreName() { + return Optional.ofNullable(getBeanName()).filter(StringUtils::hasText) + .orElse(DiskStoreFactory.DEFAULT_DISK_STORE_NAME); + } + + /** + * Creates an instance of {@link DiskStoreFactory} using the given {@link GemFireCache} in order to + * construct, configure and initialize a new {@link DiskStore}. + * + * @param cache reference to the {@link GemFireCache} used to create the {@link DiskStoreFactory}. + * @return a new instance of {@link DiskStoreFactory}. + * @see org.apache.geode.cache.GemFireCache#createDiskStoreFactory() + * @see org.apache.geode.cache.DiskStoreFactory + */ + protected DiskStoreFactory createDiskStoreFactory(GemFireCache cache) { + return cache.createDiskStoreFactory(); + } + + /** + * Configures the given {@link DiskStoreFactory} with the configuration settings present + * on this {@link DiskStoreFactoryBean} + * + * @param diskStoreFactory {@link DiskStoreFactory} to configure. + * @return the given {@link DiskStoreFactory} + * @see org.apache.geode.cache.DiskStoreFactory + */ + protected DiskStoreFactory configure(DiskStoreFactory diskStoreFactory) { + + Optional.ofNullable(this.allowForceCompaction).ifPresent(diskStoreFactory::setAllowForceCompaction); + Optional.ofNullable(this.autoCompact).ifPresent(diskStoreFactory::setAutoCompact); + Optional.ofNullable(this.compactionThreshold).ifPresent(diskStoreFactory::setCompactionThreshold); + Optional.ofNullable(this.diskUsageCriticalPercentage).ifPresent(diskStoreFactory::setDiskUsageCriticalPercentage); + Optional.ofNullable(this.diskUsageWarningPercentage).ifPresent(diskStoreFactory::setDiskUsageWarningPercentage); + Optional.ofNullable(this.maxOplogSize).ifPresent(diskStoreFactory::setMaxOplogSize); + Optional.ofNullable(this.queueSize).ifPresent(diskStoreFactory::setQueueSize); + Optional.ofNullable(this.timeInterval).ifPresent(diskStoreFactory::setTimeInterval); + Optional.ofNullable(this.writeBufferSize).ifPresent(diskStoreFactory::setWriteBufferSize); + + Optional.ofNullable(this.diskDirs).filter(diskDirs -> !CollectionUtils.isEmpty(diskDirs)) + .ifPresent(diskDirs -> { + + File[] diskDirFiles = new File[diskDirs.size()]; + int[] diskDirSizes = new int[diskDirs.size()]; + + for (int index = 0; index < diskDirs.size(); index++) { + DiskDir diskDir = diskDirs.get(index); + diskDirFiles[index] = new File(diskDir.location); + diskDirSizes[index] = Optional.ofNullable(diskDir.maxSize) + .orElse(DiskStoreFactory.DEFAULT_DISK_DIR_SIZE); + } + + diskStoreFactory.setDiskDirsAndSizes(diskDirFiles, diskDirSizes); + }); + + return diskStoreFactory; + } + + /** + * Constructs a new instance of {@link DiskStore} with the given {@link String name} + * using the provided {@link DiskStoreFactory} + * + * @param diskStoreFactory {@link DiskStoreFactory} used to create the {@link DiskStore}. + * @param diskStoreName {@link String} containing the name of the new {@link DiskStore}. + * @return a new instance of {@link DiskStore} with the given {@link String name}. + * @see org.apache.geode.cache.DiskStoreFactory + * @see org.apache.geode.cache.DiskStore + */ + protected DiskStore newDiskStore(DiskStoreFactory diskStoreFactory, String diskStoreName) { + return diskStoreFactory.create(diskStoreName); + } + + /** + * Post-process the {@link DiskStoreFactory} with any custom {@link DiskStoreFactory} or {@link DiskStore} + * configuration settings as required by the application. + * + * @param diskStoreFactory {@link DiskStoreFactory} to process. + * @return the given {@link DiskStoreFactory}. + * @see org.apache.geode.cache.DiskStoreFactory + */ + protected DiskStoreFactory postProcess(DiskStoreFactory diskStoreFactory) { + return diskStoreFactory; + } + + /** + * Post-process the provided {@link DiskStore} constructed, configured and initialized + * by this {@link DiskStoreFactoryBean}. + * + * @param diskStore {@link DiskStore} to process. + * @return the given {@link DiskStore}. + * @see org.apache.geode.cache.DiskStore + */ + protected DiskStore postProcess(DiskStore diskStore) { + return diskStore; + } + + /** + * Returns a reference to the Composite {@link DiskStoreConfigurer} used to apply additional configuration + * to this {@link DiskStoreFactoryBean} on Spring container initialization. + * + * @return the Composite {@link DiskStoreConfigurer}. + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfigurer + */ + protected DiskStoreConfigurer getCompositeDiskStoreConfigurer() { + return this.compositeDiskStoreConfigurer; + } + + @Override + public DiskStore getObject() throws Exception { + return this.diskStore; + } + + @Override + @SuppressWarnings("unchecked") + public Class getObjectType() { + return Optional.ofNullable(this.diskStore).map(DiskStore::getClass).orElse((Class) DiskStore.class); } public void setCache(GemFireCache cache) { @@ -143,26 +263,47 @@ public class DiskStoreFactoryBean implements BeanNameAware , FactoryBean= 0 && compactionThreshold <= 100), String.format("The DiskStore's (%1$s) compaction threshold (%2$d) must be an integer value between 0 and 100 inclusive.", - this.name, compactionThreshold)); + resolveDiskStoreName(), compactionThreshold)); } public void setDiskDirs(List diskDirs) { this.diskDirs = diskDirs; } + /** + * Null-safe operation to set an array of {@link DiskStoreConfigurer DiskStoreConfigurers} used to + * apply additional configuration to this {@link DiskStoreFactoryBean} when using Annotation-based configuration. + * + * @param diskStoreConfigurers array of {@link DiskStoreConfigurer DiskStoreConfigurers} used to apply + * additional configuration to this {@link DiskStoreFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfigurer + * @see #setDiskStoreConfigurers(List) + */ + public void setDiskStoreConfigurers(DiskStoreConfigurer... diskStoreConfigurers) { + setDiskStoreConfigurers(Arrays.asList(nullSafeArray(diskStoreConfigurers, DiskStoreConfigurer.class))); + } + + /** + * Null-safe operation to set an {@link Iterable} of {@link DiskStoreConfigurer DiskStoreConfigurers} + * used to apply additional configuration to this {@link DiskStoreFactoryBean} + * when using Annotation-based configuration. + * + * @param diskStoreConfigurers {@link Iterable } of {@link DiskStoreConfigurer DiskStoreConfigurers} used to + * apply additional configuration to this {@link DiskStoreFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfigurer + */ + public void setDiskStoreConfigurers(List diskStoreConfigurers) { + this.diskStoreConfigurers = Optional.ofNullable(diskStoreConfigurers).orElseGet(Collections::emptyList); + } + public void setDiskUsageCriticalPercentage(Float diskUsageCriticalPercentage) { this.diskUsageCriticalPercentage = diskUsageCriticalPercentage; } @@ -187,11 +328,8 @@ public class DiskStoreFactoryBean implements BeanNameAware , FactoryBean, BeanNameAware, BeanFactoryAware { +public class IndexFactoryBean extends AbstractFactoryBeanSupport implements InitializingBean { private boolean define = false; private boolean override = true; - private BeanFactory beanFactory; - private Index index; private IndexType indexType; + //@Autowired(required = false) + private List indexConfigurers = Collections.emptyList(); + + private IndexConfigurer compositeIndexConfigurer = new IndexConfigurer() { + + @Override + public void configure(String beanName, IndexFactoryBean bean) { + nullSafeCollection(indexConfigurers).forEach(indexConfigurer -> indexConfigurer.configure(beanName, bean)); + } + }; + private QueryService queryService; private RegionService cache; - private String beanName; private String expression; private String from; private String imports; @@ -83,29 +100,66 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B */ @Override public void afterPropertiesSet() throws Exception { - Assert.notNull(cache, "The GemFire Cache reference must not be null!"); - queryService = lookupQueryService(); + this.indexName = Optional.ofNullable(this.name).filter(StringUtils::hasText).orElse(getBeanName()); + + Assert.hasText(this.indexName, "Index name is required"); + + applyIndexConfigurers(this.indexName); + + Assert.notNull(cache, "Cache is required"); + + this.queryService = lookupQueryService(); Assert.notNull(queryService, "QueryService is required to create an Index"); - Assert.hasText(expression, "Index 'expression' is required"); - Assert.hasText(from, "Index 'from clause' is required"); + Assert.hasText(expression, "Index expression is required"); + Assert.hasText(from, "Index from clause is required"); if (IndexType.isKey(indexType)) { - Assert.isNull(imports, "'imports' are not supported with a KEY Index"); + Assert.isNull(imports, "imports are not supported with a KEY Index"); } - indexName = (StringUtils.hasText(name) ? name : beanName); + this.index = createIndex(queryService, indexName); + } - Assert.hasText(indexName, "Index 'name' is required"); + /* (non-Javadoc) */ + private void applyIndexConfigurers(String indexName) { + applyIndexConfigurers(indexName, getCompositeRegionConfigurer()); + } - index = createIndex(queryService, indexName); + /** + * Null-safe operation to apply the given array of {@link IndexConfigurer IndexConfigurers} + * to this {@link IndexFactoryBean}. + * + * @param indexName {@link String} containing the name of the {@link Index}. + * @param indexConfigurers array of {@link IndexConfigurer IndexConfigurers} applied + * to this {@link IndexFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + * @see #applyIndexConfigurers(String, Iterable) + */ + protected void applyIndexConfigurers(String indexName, IndexConfigurer... indexConfigurers) { + applyIndexConfigurers(indexName, Arrays.asList(nullSafeArray(indexConfigurers, IndexConfigurer.class))); + } + + /** + * Null-safe operation to apply the given {@link Iterable} of {@link IndexConfigurer IndexConfigurers} + * to this {@link IndexFactoryBean}. + * + * @param indexName {@link String} containing the name of the {@link Index}. + * @param indexConfigurers {@link Iterable} of {@link IndexConfigurer IndexConfigurers} applied + * to this {@link IndexFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + */ + protected void applyIndexConfigurers(String indexName, Iterable indexConfigurers) { + stream(nullSafeIterable(indexConfigurers).spliterator(), false) + .forEach(indexConfigurer -> indexConfigurer.configure(indexName, this)); } /* (non-Javadoc) */ QueryService doLookupQueryService() { - return (queryService != null ? queryService - : (cache instanceof ClientCache ? ((ClientCache) cache).getLocalQueryService() : cache.getQueryService())); + return Optional.ofNullable(this.queryService).orElseGet(() -> + (this.cache instanceof ClientCache ? ((ClientCache) this.cache).getLocalQueryService() + : this.cache.getQueryService())); } /* (non-Javadoc) */ @@ -131,6 +185,7 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B /* (non-Javadoc) */ Index createIndex(QueryService queryService, String indexName) throws Exception { + Index existingIndex = getExistingIndex(queryService, indexName); if (existingIndex != null) { @@ -183,6 +238,7 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B /* (non-Javadoc) */ Index createKeyIndex(QueryService queryService, String indexName, String expression, String from) throws Exception { + if (isDefine()) { queryService.defineKeyIndex(indexName, expression, from); return new IndexWrapper(queryService, indexName); @@ -194,7 +250,7 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B /* (non-Javadoc) */ Index createHashIndex(QueryService queryService, String indexName, String expression, String from, - String imports) throws Exception { + String imports) throws Exception { boolean hasImports = StringUtils.hasText(imports); @@ -246,7 +302,8 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B /* (non-Javadoc) */ Index getExistingIndex(QueryService queryService, String indexName) { - for (Index index : CollectionUtils.nullSafeCollection(queryService.getIndexes())) { + + for (Index index : nullSafeCollection(queryService.getIndexes())) { if (index.getName().equalsIgnoreCase(indexName)) { return index; } @@ -255,29 +312,33 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B return null; } + /** + * Returns a reference to the Composite {@link IndexConfigurer} used to apply additional configuration + * to this {@link IndexFactoryBean} on Spring container initialization. + * + * @return the Composite {@link IndexConfigurer}. + * @see org.springframework.data.gemfire.config.annotation.IndexConfigurer + */ + protected IndexConfigurer getCompositeRegionConfigurer() { + return this.compositeIndexConfigurer; + } + /** * @inheritDoc */ @Override public Index getObject() { - index = (index != null ? index : getExistingIndex(queryService, indexName)); - return index; + return Optional.ofNullable(this.index) + .orElseGet(() -> this.index = getExistingIndex(queryService, indexName)); } /** * @inheritDoc */ @Override + @SuppressWarnings("unchecked") public Class getObjectType() { - return (index != null ? index.getClass() : Index.class); - } - - /** - * @inheritDoc - */ - @Override - public boolean isSingleton() { - return true; + return Optional.ofNullable(this.index).map(Index::getClass).orElse((Class) Index.class); } /** @@ -298,24 +359,6 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B this.queryService = service; } - /* (non-Javadoc) */ - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - /* (non-Javadoc) */ - protected BeanFactory getBeanFactory() { - Assert.state(beanFactory != null, "'beanFactory' was not properly initialized"); - return beanFactory; - } - - /* (non-Javadoc) */ - @Override - public void setBeanName(String name) { - this.beanName = name; - } - /** * @param name the name to set */ @@ -363,6 +406,31 @@ public class IndexFactoryBean implements InitializingBean, FactoryBean, B this.imports = imports; } + /** + * Null-safe operation to set an array of {@link IndexConfigurer IndexConfigurers} used to apply + * additional configuration to this {@link IndexFactoryBean} when using Annotation-based configuration. + * + * @param indexConfigurers array of {@link IndexConfigurer IndexConfigurers} used to apply + * additional configuration to this {@link IndexFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.IndexConfigurer + * @see #setIndexConfigurers(List) + */ + public void setIndexConfigurers(IndexConfigurer... indexConfigurers) { + setIndexConfigurers(Arrays.asList(nullSafeArray(indexConfigurers, IndexConfigurer.class))); + } + + /** + * Null-safe operation to set an {@link Iterable} of {@link IndexConfigurer IndexConfigurers} used to apply + * additional configuration to this {@link IndexFactoryBean} when using Annotation-based configuration. + * + * @param indexConfigurers {@link Iterable } of {@link IndexConfigurer IndexConfigurers} used to apply + * additional configuration to this {@link IndexFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.IndexConfigurer + */ + public void setIndexConfigurers(List indexConfigurers) { + this.indexConfigurers = Optional.ofNullable(indexConfigurers).orElseGet(Collections::emptyList); + } + /** * @param override the override to set */ diff --git a/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java index e9ae755d..85084138 100644 --- a/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java @@ -16,7 +16,19 @@ package org.springframework.data.gemfire; +import static java.util.Arrays.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; + import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.StreamSupport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -40,30 +52,49 @@ import org.apache.geode.cache.asyncqueue.AsyncEventQueue; import org.apache.geode.cache.wan.GatewaySender; import org.apache.geode.internal.cache.UserSpecifiedRegionAttributes; import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; import org.springframework.context.SmartLifecycle; import org.springframework.core.io.Resource; import org.springframework.data.gemfire.client.ClientRegionFactoryBean; -import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.data.gemfire.config.annotation.RegionConfigurer; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; /** - * Base class for FactoryBeans used to create GemFire {@link Region}s. Will try - * to first locate the region (by name) and, in case none if found, proceed to - * creating one using the given settings. + * Abstract Spring {@link FactoryBean} base class extended by other SDG {@link FactoryBean FactoryBeans} used to + * construct, configure and initialize peer {@link Region Regions}. * - * Note that this factory bean allows for very flexible creation of GemFire - * {@link Region}. For "client" regions however, see - * {@link ClientRegionFactoryBean} which offers easier configuration and - * defaults. + * This {@link FactoryBean} allows for very easy and flexible creation of peer {@link Region}. + * For client {@link Region Regions}, however, see the {@link ClientRegionFactoryBean}. * * @author Costin Leau * @author David Turanski * @author John Blum + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.CacheListener + * @see org.apache.geode.cache.CacheLoader + * @see org.apache.geode.cache.CacheWriter + * @see org.apache.geode.cache.DataPolicy + * @see org.apache.geode.cache.EvictionAttributes + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.PartitionAttributes + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.RegionAttributes + * @see org.apache.geode.cache.RegionFactory + * @see org.apache.geode.cache.RegionShortcut + * @see org.apache.geode.cache.Scope + * @see org.apache.geode.cache.asyncqueue.AsyncEventQueue + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.context.SmartLifecycle + * @see org.springframework.data.gemfire.RegionLookupFactoryBean + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer */ @SuppressWarnings("unused") -public abstract class RegionFactoryBean extends RegionLookupFactoryBean implements DisposableBean, SmartLifecycle { +public abstract class RegionFactoryBean extends RegionLookupFactoryBean + implements DisposableBean, SmartLifecycle { protected final Log log = LogFactory.getLog(getClass()); @@ -91,8 +122,19 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean regionConfigurers = Collections.emptyList(); + private RegionAttributes attributes; + private RegionConfigurer compositeRegionConfigurer = new RegionConfigurer() { + + @Override + public void configure(String beanName, RegionFactoryBean bean) { + nullSafeCollection(regionConfigurers) + .forEach(regionConfigurer -> regionConfigurer.configure(beanName, bean)); + } + }; + private RegionShortcut shortcut; private Resource snapshot; @@ -102,129 +144,215 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean createRegion(GemFireCache gemfireCache, String regionName) throws Exception { + + applyRegionConfigurers(regionName); + + verifyLockGrantorEligibility(getAttributes(), getScope()); + + Cache cache = resolveCache(gemfireCache); + + RegionFactory regionFactory = postProcess(configure(createRegionFactory(cache))); + + Region region = newRegion(regionFactory, getParent(), regionName); + + return enableAsLockGrantor(region); + } + + /* (non-Javadoc) */ + private void applyRegionConfigurers(String regionName) { + applyRegionConfigurers(regionName, getCompositeRegionConfigurer()); } /** - * @inheritDoc + * Null-safe operation to apply the given array of {@link RegionConfigurer RegionConfigurers} + * to this {@link RegionFactoryBean}. + * + * @param regionName {@link String} containing the name of the {@link Region}. + * @param regionConfigurers array of {@link RegionConfigurer RegionConfigurers} applied + * to this {@link RegionFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + * @see #applyRegionConfigurers(String, Iterable) */ - @Override - @SuppressWarnings({ "unchecked" }) - protected Region lookupRegion(GemFireCache gemfireCache, String regionName) throws Exception { - Assert.isTrue(gemfireCache instanceof Cache, String.format("Unable to create Regions from '%1$s'.", - gemfireCache)); + protected void applyRegionConfigurers(String regionName, RegionConfigurer... regionConfigurers) { + applyRegionConfigurers(regionName, Arrays.asList(nullSafeArray(regionConfigurers, RegionConfigurer.class))); + } - Cache cache = (Cache) gemfireCache; + /** + * Null-safe operation to apply the given {@link Iterable} of {@link RegionConfigurer RegionConfigurers} + * to this {@link RegionFactoryBean}. + * + * @param regionName {@link String} containing the name of the {@link Region}. + * @param regionConfigurers {@link Iterable} of {@link RegionConfigurer RegionConfigurers} applied + * to this {@link RegionFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + */ + protected void applyRegionConfigurers(String regionName, Iterable regionConfigurers) { + StreamSupport.stream(nullSafeIterable(regionConfigurers).spliterator(), false) + .forEach(regionConfigurer -> regionConfigurer.configure(regionName, this)); + } - RegionFactory regionFactory = createRegionFactory(cache); + /* (non-Javadoc) */ + private Region enableAsLockGrantor(Region region) { - for (AsyncEventQueue asyncEventQueue : ArrayUtils.nullSafeArray(asyncEventQueues, AsyncEventQueue.class)) { - regionFactory.addAsyncEventQueueId(asyncEventQueue.getId()); - } - - for (CacheListener listener : ArrayUtils.nullSafeArray(cacheListeners, CacheListener.class)) { - regionFactory.addCacheListener(listener); - } - - if (cacheLoader != null) { - regionFactory.setCacheLoader(cacheLoader); - } - - if (cacheWriter != null) { - regionFactory.setCacheWriter(cacheWriter); - } - - resolveDataPolicy(regionFactory, persistent, dataPolicy); - - if (isDiskStoreConfigurationAllowed()) { - regionFactory.setDiskStoreName(diskStoreName); - } - - if (evictionAttributes != null) { - regionFactory.setEvictionAttributes(evictionAttributes); - } - - for (GatewaySender gatewaySender : ArrayUtils.nullSafeArray(gatewaySenders, GatewaySender.class)) { - regionFactory.addGatewaySenderId(((GatewaySender) gatewaySender).getId()); - } - - if (keyConstraint != null) { - regionFactory.setKeyConstraint(keyConstraint); - } - - if (scope != null) { - regionFactory.setScope(scope); - } - - if (valueConstraint != null) { - regionFactory.setValueConstraint(valueConstraint); - } - - if (attributes != null) { - Assert.state(!attributes.isLockGrantor() || (scope == null) || scope.isGlobal(), - "Lock Grantor only applies to a 'GLOBAL' scoped Region."); - } - - postProcess(regionFactory); - - Region region = (getParent() != null ? regionFactory.createSubregion(getParent(), regionName) - : regionFactory.create(regionName)); - - if (log.isInfoEnabled()) { - if (getParent() != null) { - log.info(String.format("Created new Cache sub-Region [%1$s] under parent Region [%2$s].", - regionName, getParent().getName())); - } - else { - log.info(String.format("Created new Cache Region [%1$s].", regionName)); - } - } - - if (snapshot != null) { - region.loadSnapshot(snapshot.getInputStream()); - } - - if (attributes != null && attributes.isLockGrantor()) { - region.becomeLockGrantor(); - } + Optional.ofNullable(region) + .filter(it -> it.getAttributes().isLockGrantor()) + .ifPresent(Region::becomeLockGrantor); return region; } + /* (non-Javadoc) */ + private Region newRegion(RegionFactory regionFactory, Region parentRegion, String regionName) { + + return Optional.ofNullable(parentRegion) + .map(parent -> { + logInfo("Creating Subregion [%1$s] with parent Region [%2$s]", + regionName, parent.getName()); + + return regionFactory.createSubregion(parent, regionName); + }) + .orElseGet(() -> { + logInfo("Created Region [%1$s]", regionName); + + return regionFactory.create(regionName); + }); + } + + /* (non-Javadoc) */ + private Cache resolveCache(GemFireCache gemfireCache) { + + return Optional.ofNullable(gemfireCache) + .filter(cache -> cache instanceof Cache) + .map(cache -> (Cache) cache) + .orElseThrow(() -> newIllegalArgumentException("Peer Cache is required")); + } + + /* (non-Javadoc) */ + private RegionAttributes verifyLockGrantorEligibility(RegionAttributes regionAttributes, Scope scope) { + + Optional.ofNullable(regionAttributes).ifPresent(attributes -> + Assert.state(!attributes.isLockGrantor() || verifyScope(scope), + "Lock Grantor only applies to GLOBAL Scoped Regions")); + + return regionAttributes; + } + + /* (non-Javadoc) */ + private boolean verifyScope(Scope scope) { + return (scope == null || Scope.GLOBAL.equals(scope)); + } + /** - * Creates an instance of RegionFactory using the given Cache instance used to configure and construct the Region - * created by this FactoryBean. + * Creates an instance of {@link RegionFactory} with the given {@link Cache} which is then used to construct, + * configure and initialize the {@link Region} specified by this {@link RegionFactoryBean}. * - * @param cache the GemFire Cache instance. - * @return a RegionFactory used to configure and construct the Region created by this FactoryBean. - * @see org.apache.geode.cache.Cache#createRegionFactory() - * @see org.apache.geode.cache.Cache#createRegionFactory(org.apache.geode.cache.RegionAttributes) + * @param cache reference to the {@link Cache}. + * @return a {@link RegionFactory} used to construct, configure and initialized the {@link Region} specified by + * this {@link RegionFactoryBean}. * @see org.apache.geode.cache.Cache#createRegionFactory(org.apache.geode.cache.RegionShortcut) + * @see org.apache.geode.cache.Cache#createRegionFactory(org.apache.geode.cache.RegionAttributes) + * @see org.apache.geode.cache.Cache#createRegionFactory() * @see org.apache.geode.cache.RegionFactory */ protected RegionFactory createRegionFactory(Cache cache) { - if (shortcut != null) { - RegionFactory regionFactory = mergeRegionAttributes( - cache.createRegionFactory(shortcut), attributes); + + if (this.shortcut != null) { + RegionFactory regionFactory = + mergeRegionAttributes(cache.createRegionFactory(this.shortcut), this.attributes); + setDataPolicy(getDataPolicy(regionFactory)); + return regionFactory; } - else if (attributes != null) { - return cache.createRegionFactory(attributes); + else if (this.attributes != null) { + return cache.createRegionFactory(this.attributes); } else { return cache.createRegionFactory(); } } + /** + * Configures the {@link RegionFactory} based on the configuration settings of this {@link RegionFactoryBean}. + * + * @param regionFactory {@link RegionFactory} to configure + * @return the given {@link RegionFactory}. + * @see org.apache.geode.cache.RegionFactory + */ + protected RegionFactory configure(RegionFactory regionFactory) { + + stream(nullSafeArray(this.asyncEventQueues, AsyncEventQueue.class)) + .forEach(asyncEventQueue -> regionFactory.addAsyncEventQueueId(asyncEventQueue.getId())); + + stream(nullSafeArray(this.cacheListeners, CacheListener.class)).forEach(regionFactory::addCacheListener); + + Optional.ofNullable(this.cacheLoader).ifPresent(regionFactory::setCacheLoader); + + Optional.ofNullable(this.cacheWriter).ifPresent(regionFactory::setCacheWriter); + + resolveDataPolicy(regionFactory, persistent, dataPolicy); + + Optional.ofNullable(this.diskStoreName) + .filter(name -> isDiskStoreConfigurationAllowed()) + .ifPresent(regionFactory::setDiskStoreName); + + Optional.ofNullable(this.evictionAttributes).ifPresent(regionFactory::setEvictionAttributes); + + stream(nullSafeArray(this.gatewaySenders, GatewaySender.class)) + .forEach(gatewaySender -> regionFactory.addGatewaySenderId(((GatewaySender) gatewaySender).getId())); + + Optional.ofNullable(this.keyConstraint).ifPresent(regionFactory::setKeyConstraint); + + Optional.ofNullable(this.scope).ifPresent(regionFactory::setScope); + + Optional.ofNullable(this.valueConstraint).ifPresent(regionFactory::setValueConstraint); + + return regionFactory; + } + + /** + * Post-process the {@link RegionFactory} used to create the {@link Region} specified by + * this {@link RegionFactoryBean} during initialization. + * + * The {@link RegionFactory} has been already constructed, configured and initialized by + * this {@link RegionFactoryBean} before this method gets invoked. + * + * @param regionFactory {@link RegionFactory} used to create the {@link Region}. + * @return the given {@link RegionFactory}. + * @see org.apache.geode.cache.RegionFactory + */ + protected RegionFactory postProcess(RegionFactory regionFactory) { + + regionFactory.setOffHeap(Boolean.TRUE.equals(this.offHeap)); + + return regionFactory; + } + + /** + * Returns a reference to the Composite {@link RegionConfigurer} used to apply additional configuration + * to this {@link RegionFactoryBean} on Spring container initialization. + * + * @return the Composite {@link RegionConfigurer}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + */ + protected RegionConfigurer getCompositeRegionConfigurer() { + return this.compositeRegionConfigurer; + } + /* - * (non-Javadoc) - This method should not be considered part of the RegionFactoryBean API - * and is strictly for testing purposes! + * (non-Javadoc) + * + * This method is not considered part of the RegionFactoryBean API and is strictly used for testing purposes! * * NOTE cannot pass RegionAttributes.class as the "targetType" in the second invocation of getFieldValue(..) * since the "regionAttributes" field is naively declared as a instance of the implementation class type @@ -238,8 +366,8 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean extends RegionLookupFactoryBean extends RegionLookupFactoryBean void mergePartitionAttributes(final RegionFactory regionFactory, - final RegionAttributes regionAttributes) { + /** + * + * @param regionFactory + * @param regionAttributes + */ + protected void mergePartitionAttributes(RegionFactory regionFactory, + RegionAttributes regionAttributes) { // NOTE PartitionAttributes are created by certain RegionShortcuts; need the null check since RegionAttributes // can technically return null! @@ -348,8 +482,9 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean extends RegionLookupFactoryBean extends RegionLookupFactoryBean extends RegionLookupFactoryBean regionFactory) { - regionFactory.setOffHeap(Boolean.TRUE.equals(offHeap)); - } - - /** - * Post-process the Region for this factory bean during the initialization process. The Region is - * already configured and initialized by the factory bean before this method is invoked. - * - * @param region the GemFire Region to post-process. - * @see org.apache.geode.cache.Region - */ - protected Region postProcess(Region region) { - return region; - } - /** * Validates and sets the Data Policy on the RegionFactory used to create and configure the Region from this * FactoryBean. @@ -478,6 +594,7 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean regionFactory, Boolean persistent, DataPolicy dataPolicy) { + if (dataPolicy != null) { assertDataPolicyAndPersistentAttributesAreCompatible(dataPolicy); regionFactory.setDataPolicy(dataPolicy); @@ -499,10 +616,11 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean regionFactory, Boolean persistent, String dataPolicy) { + if (dataPolicy != null) { DataPolicy resolvedDataPolicy = new DataPolicyConverter().convert(dataPolicy); - Assert.notNull(resolvedDataPolicy, String.format("Data Policy '%1$s' is invalid.", dataPolicy)); + Assert.notNull(resolvedDataPolicy, String.format("Data Policy [%1$s] is invalid.", dataPolicy)); assertDataPolicyAndPersistentAttributesAreCompatible(resolvedDataPolicy); regionFactory.setDataPolicy(resolvedDataPolicy); @@ -521,16 +639,21 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean region = getObject(); - if (region != null) { - if (close) { + Optional.ofNullable(getObject()).ifPresent(region -> { + if (this.close) { if (!region.getRegionService().isClosed()) { try { region.close(); @@ -541,10 +664,10 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean extends RegionLookupFactoryBean region = getRegion(); - return (region != null ? region.getAttributes() : attributes); + public RegionAttributes getAttributes() { + return Optional.ofNullable(getRegion()).map(Region::getAttributes).orElse(this.attributes); } /** @@ -662,8 +784,8 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean newIllegalStateException("Data Policy has not been properly resolved yet")); } /** @@ -706,7 +828,7 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean extends RegionLookupFactoryBean regionConfigurers) { + this.regionConfigurers = Optional.ofNullable(regionConfigurers).orElseGet(Collections::emptyList); + } + + public Scope getScope() { + return this.scope; + } + /** * Sets the region scope. Used only when a new region is created. Overrides * the settings specified through {@link #setAttributes(RegionAttributes)}. @@ -749,23 +900,10 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBeancreated region. That - * is, the snapshot will be used only when a new region is created - - * if the region already exists, no loading will be performed. - * - * @see #setName(String) - * @param snapshot the snapshot to set - */ - public void setSnapshot(Resource snapshot) { - this.snapshot = snapshot; - } - public void setValueConstraint(Class valueConstraint) { this.valueConstraint = valueConstraint; } @@ -776,6 +914,7 @@ public abstract class RegionFactoryBean extends RegionLookupFactoryBean extends RegionLookupFactoryBean - implements FactoryBean>, InitializingBean, BeanNameAware { - - protected final Log log = LogFactory.getLog(getClass()); +public abstract class RegionLookupFactoryBean extends AbstractFactoryBeanSupport> + implements InitializingBean { private Boolean lookupEnabled = false; @@ -52,60 +56,109 @@ public abstract class RegionLookupFactoryBean private Region parent; + private Resource snapshot; + private volatile Region region; - private String beanName; private String name; private String regionName; /** - * @inheritDoc + * Initializes this {@link RegionLookupFactoryBean} after properties have been set by the Spring container. + * + * @throws Exception if initialization fails. + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + * @see #createRegion(GemFireCache, String) */ @Override @SuppressWarnings("all") public void afterPropertiesSet() throws Exception { - Assert.notNull(this.cache, "A 'Cache' reference must be set"); + + GemFireCache cache = getCache(); + + Assert.notNull(cache, "Cache is required"); String regionName = resolveRegionName(); - Assert.hasText(regionName, "'regionName', 'name' or 'beanName' property must be set"); + Assert.hasText(regionName, "regionName, name or beanName property must be set"); - synchronized (this.cache) { - if (isLookupEnabled()) { - this.region = Optional.ofNullable(getParent()) + synchronized (cache) { + setRegion(isLookupEnabled() + ? Optional.ofNullable(getParent()) .map(parentRegion -> parentRegion.getSubregion(regionName)) - .orElseGet(() -> this.cache.getRegion(regionName)); - } + .orElseGet(() -> cache.getRegion(regionName)) + : null); - if (region != null) { - log.info(String.format("Found Region [%1$s] in Cache [%2$s]", regionName, cache.getName())); + if (getRegion() != null) { + logInfo("Found Region [%1$s] in Cache [%2$s]", regionName, cache.getName()); } else { - log.info(String.format("Falling back to creating Region [%1$s] in Cache [%2$s]", - regionName, cache.getName())); + logInfo("Falling back to creating Region [%1$s] in Cache [%2$s]", + regionName, cache.getName()); - region = lookupRegion(cache, regionName); + setRegion(postProcess(loadSnapshot(createRegion(cache, regionName)))); } } } /** - * Method to perform a lookup when the named {@link Region} does not exist. By default, this implementation - * throws an exception. + * Creates a new {@link Region} with the given {@link String name}. * - * @param cache reference to the GemFire cache. - * @param regionName name of the GemFire {@link Region}. - * @return the {@link Region} in the GemFire cache with the given name. - * @throws BeanInitializationException if the lookup operation fails. + * This method gets called when a {@link Region} with the specified {@link String name} does not already exist. + * By default, this method implementation throws a {@link BeanInitializationException} and it is expected + * that {@link Class subclasses} will override this method. + * + * @param cache reference to the {@link GemFireCache}. + * @param regionName {@link String name} of the new {@link Region}. + * @return a new {@link Region} with the given {@link String name}. + * @throws BeanInitializationException by default unless a {@link Class subclass} overrides this method. + * @see org.apache.geode.cache.GemFireCache * @see org.apache.geode.cache.Region */ - protected Region lookupRegion(GemFireCache cache, String regionName) throws Exception { - throw new BeanInitializationException(String.format( - "Region [%1$s] in Cache [%2$s] not found", regionName, cache)); + protected Region createRegion(GemFireCache cache, String regionName) throws Exception { + throw new BeanInitializationException( + String.format("Region [%1$s] in Cache [%2$s] not found", regionName, cache)); } /** - * @inheritDoc + * Loads the configured data {@link Resource snapshot} into the given {@link Region}. + * + * @param region {@link Region} to load. + * @return the given {@link Region}. + * @throws RuntimeException if the snapshot load fails. + * @see org.apache.geode.cache.Region#loadSnapshot(InputStream) + */ + protected Region loadSnapshot(Region region) { + + Optional.ofNullable(this.snapshot).ifPresent(snapshot -> { + try { + region.loadSnapshot(snapshot.getInputStream()); + } + catch (Exception e) { + throw newRuntimeException(e, "Failed to load snapshot [%s]", snapshot); + } + }); + + return region; + } + + /** + * Post-process the {@link Region} created by this {@link RegionFactoryBean}. + * + * @param region {@link Region} to process. + * @see org.apache.geode.cache.Region + */ + protected Region postProcess(Region region) { + return region; + } + + /** + * Returns an object reference to the {@link Region} created by this {@link RegionLookupFactoryBean}. + * + * @return an object reference to the {@link Region} created by this {@link RegionLookupFactoryBean}. + * @see org.springframework.beans.factory.FactoryBean#getObject() + * @see org.apache.geode.cache.Region + * @see #getRegion() */ @Override public Region getObject() throws Exception { @@ -113,49 +166,42 @@ public abstract class RegionLookupFactoryBean } /** - * @inheritDoc - */ - @Override - public Class getObjectType() { - Region region = getRegion(); - return (region != null ? region.getClass() : Region.class); - } - - /** - * @inheritDoc - */ - @Override - public boolean isSingleton() { - return true; - } - - /** - * Resolves the name of the GemFire {@link Region}. + * Returns the {@link Class} type of the {@link Region} produced by this {@link RegionLookupFactoryBean}. * - * @return a {@link String} indicating the name of the GemFire {@link Region}. + * @return the {@link Class} type of the {@link Region} produced by this {@link RegionLookupFactoryBean}. + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + @Override + @SuppressWarnings("unchecked") + public Class getObjectType() { + return Optional.ofNullable(getRegion()).map(Region::getClass).orElse((Class) Region.class); + } + + /** + * Resolves the {@link String name} of the {@link Region}. + * + * @return a {@link String} containing the name of the {@link Region}. * @see org.apache.geode.cache.Region#getName() */ public String resolveRegionName() { return (StringUtils.hasText(this.regionName) ? this.regionName - : (StringUtils.hasText(this.name) ? this.name : this.beanName)); + : (StringUtils.hasText(this.name) ? this.name : getBeanName())); } /** - * Sets the name of the {@link Region} based on the bean 'id' attribute. If no {@link Region} is found - * with the given name, a new one will be created. + * Returns a reference to the {@link GemFireCache} used to create the {@link Region}. * - * @param name name of this {@link Region} bean in the Spring {@link org.springframework.context.ApplicationContext}. - * @see org.springframework.beans.factory.BeanNameAware#setBeanName(String) + * @return a reference to the {@link GemFireCache} used to create the {@link Region}.. + * @see org.apache.geode.cache.GemFireCache */ - public void setBeanName(String name) { - this.beanName = name; + public GemFireCache getCache() { + return this.cache; } /** * Sets a reference to the {@link GemFireCache} used to create the {@link Region}. * * @param cache reference to the {@link GemFireCache}. - * @see org.springframework.data.gemfire.CacheFactoryBean * @see org.apache.geode.cache.GemFireCache */ public void setCache(GemFireCache cache) { @@ -174,7 +220,7 @@ public abstract class RegionLookupFactoryBean /* (non-Javadoc) */ public Boolean getLookupEnabled() { - return lookupEnabled; + return this.lookupEnabled; } /** @@ -217,13 +263,23 @@ public abstract class RegionLookupFactoryBean } /** - * Returns a reference to the GemFire {@link Region} resolved by this Spring {@link FactoryBean} - * during the lookup operation; maybe a new {@link Region}. + * Sets a reference to the {@link Region} to be resolved by this Spring {@link FactoryBean}. * - * @return a reference to the GemFire {@link Region} resolved during lookup. + * @param region reference to the resolvable {@link Region}. * @see org.apache.geode.cache.Region */ - protected Region getRegion() { + protected void setRegion(Region region) { + this.region = region; + } + + /** + * Returns a reference to the {@link Region} resolved by this Spring {@link FactoryBean} + * during the lookup operation; maybe a new {@link Region}. + * + * @return a reference to the {@link Region} resolved during lookup. + * @see org.apache.geode.cache.Region + */ + public Region getRegion() { return this.region; } @@ -238,4 +294,16 @@ public abstract class RegionLookupFactoryBean public void setRegionName(String regionName) { this.regionName = regionName; } + + /** + * Sets the snapshots used for loading a newly created region. That + * is, the snapshot will be used only when a new region is created - + * if the region already exists, no loading will be performed. + * + * @see #setName(String) + * @param snapshot the snapshot to set + */ + public void setSnapshot(Resource snapshot) { + this.snapshot = snapshot; + } } 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 b84b358e..806d5b7c 100644 --- a/src/main/java/org/springframework/data/gemfire/client/ClientCacheFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/client/ClientCacheFactoryBean.java @@ -16,9 +16,19 @@ package org.springframework.data.gemfire.client; +import static java.util.stream.StreamSupport.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; + import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.geode.cache.CacheClosedException; import org.apache.geode.cache.GemFireCache; @@ -27,34 +37,32 @@ import org.apache.geode.cache.client.ClientCacheFactory; import org.apache.geode.cache.client.Pool; import org.apache.geode.cache.client.PoolManager; import org.apache.geode.distributed.DistributedSystem; -import org.apache.geode.pdx.PdxSerializer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationContextEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.data.gemfire.CacheFactoryBean; import org.springframework.data.gemfire.GemfireUtils; import org.springframework.data.gemfire.client.support.DefaultableDelegatingPoolAdapter; import org.springframework.data.gemfire.client.support.DelegatingPoolAdapter; +import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer; import org.springframework.data.gemfire.config.xml.GemfireConstants; import org.springframework.data.gemfire.support.ConnectionEndpoint; import org.springframework.data.gemfire.support.ConnectionEndpointList; import org.springframework.data.gemfire.util.SpringUtils; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** - * FactoryBean dedicated to creating GemFire client caches. + * Spring {@link org.springframework.beans.factory.FactoryBean} used to create a Pivotal GemFire/Apache Geode + * {@link ClientCache}. * * @author Costin Leau * @author Lyndon Adams * @author John Blum - * @see org.springframework.context.ApplicationListener - * @see org.springframework.context.event.ContextRefreshedEvent - * @see org.springframework.data.gemfire.CacheFactoryBean - * @see org.springframework.data.gemfire.support.ConnectionEndpoint - * @see org.springframework.data.gemfire.support.ConnectionEndpointList + * @see java.net.InetSocketAddress * @see org.apache.geode.cache.GemFireCache * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.client.ClientCacheFactory @@ -62,6 +70,14 @@ import org.springframework.util.ObjectUtils; * @see org.apache.geode.cache.client.PoolManager * @see org.apache.geode.distributed.DistributedSystem * @see org.apache.geode.pdx.PdxSerializer + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.context.ApplicationContext + * @see org.springframework.context.ApplicationListener + * @see org.springframework.context.event.ContextRefreshedEvent + * @see org.springframework.data.gemfire.CacheFactoryBean + * @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer + * @see org.springframework.data.gemfire.support.ConnectionEndpoint + * @see org.springframework.data.gemfire.support.ConnectionEndpointList */ @SuppressWarnings("unused") public class ClientCacheFactoryBean extends CacheFactoryBean implements ApplicationListener { @@ -89,6 +105,8 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat private Integer subscriptionMessageTrackingTimeout; private Integer subscriptionRedundancy; + private List clientCacheConfigurers = Collections.emptyList(); + private Long idleTimeout; private Long pingInterval; @@ -98,37 +116,83 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat private String poolName; private String serverGroup; + private final ClientCacheConfigurer compositeClientCacheConfigurer = (beanName, bean) -> + nullSafeCollection(clientCacheConfigurers).forEach(clientCacheConfigurer -> + clientCacheConfigurer.configure(beanName, bean)); + + /** + * Post processes this {@link ClientCacheFactoryBean} before cache initialization. + * + * This is also the point at which any configured {@link ClientCacheConfigurer} beans are called. + * + * @param gemfireProperties {@link Properties} used to configure Pivotal GemFire/Apache Geode. + * @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer + * @see java.util.Properties + */ @Override protected void postProcessBeforeCacheInitialization(Properties gemfireProperties) { + applyClientCacheConfigurers(); + } + + /* (non-Javadoc) */ + private void applyClientCacheConfigurers() { + applyClientCacheConfigurers(this.compositeClientCacheConfigurer); } /** - * Fetches an existing GemFire ClientCache instance from the ClientCacheFactory. + * Null-safe operation to apply the given array of {@link ClientCacheConfigurer ClientCacheConfigurers} + * to this {@link ClientCacheFactoryBean}. * - * @param is Class type extension of GemFireCache. - * @return the existing GemFire ClientCache instance if available. - * @throws org.apache.geode.cache.CacheClosedException if an existing GemFire Cache instance does not exist. - * @throws java.lang.IllegalStateException if the GemFire cache instance is not a ClientCache. - * @see org.apache.geode.cache.GemFireCache + * @param clientCacheConfigurers array of {@link ClientCacheConfigurer ClientCacheConfigurers} applied to + * this {@link ClientCacheFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer + * @see #applyClientCacheConfigurers(Iterable) + */ + protected void applyClientCacheConfigurers(ClientCacheConfigurer... clientCacheConfigurers) { + applyClientCacheConfigurers(Arrays.asList(nullSafeArray(clientCacheConfigurers, ClientCacheConfigurer.class))); + } + + /** + * Null-safe operation to apply the given {@link Iterable} of {@link ClientCacheConfigurer ClientCacheConfigurers} + * to this {@link ClientCacheFactoryBean}. + * + * @param clientCacheConfigurers {@link Iterable} of {@link ClientCacheConfigurer ClientCacheConfigurers} + * applied to this {@link ClientCacheFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer + * @see java.lang.Iterable + */ + protected void applyClientCacheConfigurers(Iterable clientCacheConfigurers) { + stream(nullSafeIterable(clientCacheConfigurers).spliterator(), false) + .forEach(clientCacheConfigurer -> clientCacheConfigurer.configure(getBeanName(), this)); + } + + /** + * Fetches an existing {@link ClientCache} instance from the {@link ClientCacheFactory}. + * + * @param parameterized {@link Class} type extension of {@link GemFireCache}. + * @return an existing {@link ClientCache} instance if available. + * @throws org.apache.geode.cache.CacheClosedException if an existing {@link ClientCache} instance does not exist. * @see org.apache.geode.cache.client.ClientCacheFactory#getAnyInstance() + * @see org.apache.geode.cache.GemFireCache + * @see #getCache() */ @Override @SuppressWarnings("unchecked") protected T fetchCache() { - ClientCache cache = getCache(); - return (T) (cache != null ? cache : ClientCacheFactory.getAnyInstance()); + return (T) Optional.ofNullable(getCache()).orElseGet(ClientCacheFactory::getAnyInstance); } /** - * Resolves the GemFire System properties used to configure the GemFire ClientCache instance. + * Resolves the Pivotal GemFire/Apache Geode {@link Properties} used to configure the {@link ClientCache}. * - * @return a Properties object containing GemFire System properties used to configure - * the GemFire ClientCache instance. + * @return the resolved Pivotal GemFire/Apache Geode {@link Properties} used to configure the {@link ClientCache}. + * @see org.apache.geode.distributed.DistributedSystem#getProperties() + * @see #getDistributedSystem() */ @Override protected Properties resolveProperties() { - Properties gemfireProperties = super.resolveProperties(); + Properties gemfireProperties = super.resolveProperties(); DistributedSystem distributedSystem = getDistributedSystem(); if (GemfireUtils.isConnected(distributedSystem)) { @@ -137,24 +201,32 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat gemfireProperties = distributedSystemProperties; } - GemfireUtils.configureDurableClient(gemfireProperties, durableClientId, durableClientTimeout); + GemfireUtils.configureDurableClient(gemfireProperties, getDurableClientId(), getDurableClientTimeout()); return gemfireProperties; } - /* (non-Javadoc) */ + /** + * Returns the {@link DistributedSystem} formed from cache initialization. + * + * @param {@link Class} type of the {@link DistributedSystem}. + * @return an instance of the {@link DistributedSystem}. + * @see org.apache.geode.distributed.DistributedSystem + */ T getDistributedSystem() { return GemfireUtils.getDistributedSystem(); } /** - * Creates an instance of GemFire factory initialized with the given GemFire System Properties - * to create an instance of a GemFire cache. + * Constructs a new instance of {@link ClientCacheFactory} initialized with the given Pivotal GemFire/Apache Geode + * {@link Properties} used to create an instance of a {@link ClientCache}. * - * @param gemfireProperties a Properties object containing GemFire System properties. - * @return an instance of a GemFire factory used to create a GemFire cache instance. - * @see java.util.Properties + * @param gemfireProperties {@link Properties} used by the {@link ClientCacheFactory} + * to configure the {@link ClientCache}. + * @return a new instance of {@link ClientCacheFactory} initialized with the given Pivotal GemFire/Apache Geode + * {@link Properties}. * @see org.apache.geode.cache.client.ClientCacheFactory + * @see java.util.Properties */ @Override protected Object createFactory(Properties gemfireProperties) { @@ -162,61 +234,52 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat } /** - * Initializes the GemFire factory used to create the GemFire client cache instance. Sets PDX options - * specified by the user. + * Prepares and initializes the {@link ClientCacheFactory} used to create the {@link ClientCache}. * - * @param factory the GemFire factory used to create an instance of the GemFire client cache. - * @return the initialized GemFire client cache factory. - * @see #isPdxOptionsSpecified() + * Sets PDX options specified by the user. + * + * @param factory {@link ClientCacheFactory} used to create the {@link ClientCache}. + * @return the prepared and initialized {@link ClientCacheFactory}. + * @see #initializePdx(ClientCacheFactory) */ @Override - protected Object prepareFactory(final Object factory) { + protected Object prepareFactory(Object factory) { return initializePool(initializePdx((ClientCacheFactory) factory)); } /** - * Initialize the PDX settings on the {@link ClientCacheFactory}. + * Configure PDX for the {@link ClientCacheFactory}. * - * @param clientCacheFactory the GemFire {@link ClientCacheFactory} used to configure and create - * a GemFire {@link ClientCache}. + * @param clientCacheFactory {@link ClientCacheFactory} used to configure PDX. + * @return the given {@link ClientCacheFactory} * @see org.apache.geode.cache.client.ClientCacheFactory */ ClientCacheFactory initializePdx(ClientCacheFactory clientCacheFactory) { - if (isPdxOptionsSpecified()) { - if (getPdxSerializer() != null) { - Assert.isInstanceOf(PdxSerializer.class, getPdxSerializer(), - String.format("[%1$s] of type [%2$s] is not a PdxSerializer;", getPdxSerializer(), - ObjectUtils.nullSafeClassName(getPdxSerializer()))); - clientCacheFactory.setPdxSerializer((PdxSerializer) getPdxSerializer()); - } - if (getPdxDiskStoreName() != null) { - clientCacheFactory.setPdxDiskStore(getPdxDiskStoreName()); - } - if (getPdxIgnoreUnreadFields() != null) { - clientCacheFactory.setPdxIgnoreUnreadFields(getPdxIgnoreUnreadFields()); - } - if (getPdxPersistent() != null) { - clientCacheFactory.setPdxPersistent(getPdxPersistent()); - } - if (getPdxReadSerialized() != null) { - clientCacheFactory.setPdxReadSerialized(getPdxReadSerialized()); - } - } + Optional.ofNullable(getPdxSerializer()).ifPresent(clientCacheFactory::setPdxSerializer); + + Optional.ofNullable(getPdxDiskStoreName()).filter(StringUtils::hasText) + .ifPresent(clientCacheFactory::setPdxDiskStore); + + Optional.ofNullable(getPdxIgnoreUnreadFields()).ifPresent(clientCacheFactory::setPdxIgnoreUnreadFields); + + Optional.ofNullable(getPdxPersistent()).ifPresent(clientCacheFactory::setPdxPersistent); + + Optional.ofNullable(getPdxReadSerialized()).ifPresent(clientCacheFactory::setPdxReadSerialized); return clientCacheFactory; } /** - * Initialize the {@link Pool} settings on the {@link ClientCacheFactory} with a given {@link Pool} instance - * or named {@link Pool}. + * Configure the {@literal DEFAULT} {@link Pool} configuration settings with the {@link ClientCacheFactory} + * using a given {@link Pool} instance or a named {@link Pool}. * - * @param clientCacheFactory the GemFire {@link ClientCacheFactory} used to configure and create - * a GemFire {@link ClientCache}. + * @param clientCacheFactory {@link ClientCacheFactory} use to configure the {@literal DEFAULT} {@link Pool}. * @see org.apache.geode.cache.client.ClientCacheFactory * @see org.apache.geode.cache.client.Pool */ ClientCacheFactory initializePool(ClientCacheFactory clientCacheFactory) { + DefaultableDelegatingPoolAdapter pool = DefaultableDelegatingPoolAdapter.from( DelegatingPoolAdapter.from(resolvePool())).preferDefault(); @@ -239,48 +302,51 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat clientCacheFactory.setPoolSubscriptionRedundancy(pool.getSubscriptionRedundancy(getSubscriptionRedundancy())); clientCacheFactory.setPoolThreadLocalConnections(pool.getThreadLocalConnections(getThreadLocalConnections())); - boolean noServers = getServers().isEmpty(); - boolean hasServers = !noServers; + final AtomicBoolean noServers = new AtomicBoolean(getServers().isEmpty()); + + boolean hasServers = !noServers.get(); boolean noLocators = getLocators().isEmpty(); boolean hasLocators = !noLocators; if (hasServers || noLocators) { Iterable servers = pool.getServers(getServers().toInetSocketAddresses()); - for (InetSocketAddress server : servers) { + stream(servers.spliterator(), false).forEach(server -> { clientCacheFactory.addPoolServer(server.getHostName(), server.getPort()); - noServers = false; - } + noServers.set(false); + }); } - if (hasLocators || noServers) { + if (hasLocators || noServers.get()) { Iterable locators = pool.getLocators(getLocators().toInetSocketAddresses()); - for (InetSocketAddress locator : locators) { - clientCacheFactory.addPoolLocator(locator.getHostName(), locator.getPort()); - } + stream(locators.spliterator(), false).forEach(locator -> + clientCacheFactory.addPoolLocator(locator.getHostName(), locator.getPort())); } return clientCacheFactory; } /** - * Resolves the appropriate GemFire {@link Pool} from Spring configuration that will be used to configure - * the GemFire {@link ClientCache}. + * Resolves an appropriate {@link Pool} from the Spring container that will be used to configure + * the {@link ClientCache}. * - * @return the resolved GemFire {@link Pool}. - * @see org.apache.geode.cache.client.PoolManager#find(String) + * @return the resolved {@link Pool}. * @see org.apache.geode.cache.client.Pool + * @see #findPool(String) */ Pool resolvePool() { Pool localPool = getPool(); if (localPool == null) { - String poolName = SpringUtils.defaultIfNull(getPoolName(), GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME); + + String poolName = Optional.ofNullable(getPoolName()).filter(StringUtils::hasText) + .orElse(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME); localPool = findPool(poolName); if (localPool == null) { + BeanFactory beanFactory = getBeanFactory(); if (beanFactory instanceof ListableBeanFactory) { @@ -295,8 +361,8 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat } } catch (BeansException e) { - log.info(String.format("unable to resolve bean of type [%1$s] with name [%2$s]", - PoolFactoryBean.class.getName(), poolName)); + logInfo("Unable to resolve bean of type [%1$s] with name [%2$s]", + PoolFactoryBean.class.getName(), poolName); } } } @@ -306,11 +372,11 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat } /** - * Attempts to find a GemFire {@link Pool} with the given name. + * Attempts to find a {@link Pool} with the given {@link String name}. * - * @param name a String indicating the name of the GemFire {@link Pool} to find. - * @return a {@link Pool} instance with the given name registered in GemFire or null if no {@link Pool} - * with name exists. + * @param name {@link String} containing the name of the {@link Pool} to find. + * @return a {@link Pool} instance with the given {@link String name} registered in GemFire/Geode + * or {@literal null} if no {@link Pool} with the given {@link String name} exists. * @see org.apache.geode.cache.client.PoolManager#find(String) * @see org.apache.geode.cache.client.Pool */ @@ -319,11 +385,11 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat } /** - * Creates a new GemFire cache instance using the provided factory. + * Creates a new {@link ClientCache} instance using the provided factory. * - * @param parameterized Class type extension of {@link GemFireCache}. - * @param factory the appropriate GemFire factory used to create a cache instance. - * @return an instance of the GemFire cache. + * @param parameterized {@link Class} type extension of {@link GemFireCache}. + * @param factory instance of {@link ClientCacheFactory}. + * @return a new instance of {@link ClientCache} created by the provided factory. * @see org.apache.geode.cache.client.ClientCacheFactory#create() * @see org.apache.geode.cache.GemFireCache */ @@ -334,39 +400,48 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat } /** - * Inform the GemFire cluster that this client cache is ready to receive events iff the client is non-durable. + * Inform the Pivotal GemFire/Apache Geode cluster that this cache client is ready to receive events + * iff the client is non-durable. * - * @param event the ApplicationContextEvent fired when the ApplicationContext is refreshed. + * @param event {@link ApplicationContextEvent} fired when the {@link ApplicationContext} is refreshed. * @see org.apache.geode.cache.client.ClientCache#readyForEvents() - * @see #getReadyForEvents() - * @see #getObject() + * @see #isReadyForEvents() + * @see #fetchCache() */ @Override - public void onApplicationEvent(final ContextRefreshedEvent event) { + public void onApplicationEvent(ContextRefreshedEvent event) { if (isReadyForEvents()) { try { - ((ClientCache) fetchCache()).readyForEvents(); + this.fetchCache().readyForEvents(); } - catch (IllegalStateException ignore) { + catch (IllegalStateException | CacheClosedException ignore) { // thrown if clientCache.readyForEvents() is called on a non-durable client } - catch (CacheClosedException ignore) { - // cache is closed or was shutdown so ready-for-events is moot - } } } - /* (non-Javadoc) */ + /** + * Null-safe internal method used to close the {@link ClientCache} and preserve durability. + * + * @param cache {@link GemFireCache} to close. + * @see org.apache.geode.cache.client.ClientCache#close(boolean) + * @see #isKeepAlive() + */ @Override protected void close(GemFireCache cache) { ((ClientCache) cache).close(isKeepAlive()); } - /* (non-Javadoc) */ + /** + * Returns the {@link Class} type of the {@link GemFireCache} produced by this {@link ClientCacheFactoryBean}. + * + * @return the {@link Class} type of the {@link GemFireCache} produced by this {@link ClientCacheFactoryBean}. + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ @Override + @SuppressWarnings("unchecked") public Class getObjectType() { - ClientCache cache = getCache(); - return (cache != null ? cache.getClass() : ClientCache.class); + return Optional.ofNullable(getCache()).map(Object::getClass).orElse((Class) ClientCache.class); } /* (non-Javadoc) */ @@ -389,6 +464,42 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat this.servers.add(servers); } + /** + * Null-safe operation to set an array of {@link ClientCacheConfigurer ClientCacheConfigurers} used to apply + * additional configuration to this {@link ClientCacheFactoryBean} when using Annotation-based configuration. + * + * @param clientCacheConfigurers array of {@link ClientCacheConfigurer ClientCacheConfigurers} used to apply + * additional configuration to this {@link ClientCacheFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer + * @see #setClientCacheConfigurers(List) + */ + public void setClientCacheConfigurers(ClientCacheConfigurer... clientCacheConfigurers) { + setClientCacheConfigurers(Arrays.asList(nullSafeArray(clientCacheConfigurers, ClientCacheConfigurer.class))); + } + + /** + * Null-safe operation to set an {@link Iterable} of {@link ClientCacheConfigurer ClientCacheConfigurers} to apply + * additional configuration to this {@link ClientCacheFactoryBean} when using Annotation-based configuration. + * + * @param peerCacheConfigurers {@link Iterable} of {@link ClientCacheConfigurer ClientCacheConfigurers} used to apply + * additional configuration to this {@link ClientCacheFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer + */ + public void setClientCacheConfigurers(List peerCacheConfigurers) { + this.clientCacheConfigurers = Optional.ofNullable(peerCacheConfigurers).orElseGet(Collections::emptyList); + } + + /** + * Returns a reference to the Composite {@link ClientCacheConfigurer} used to apply additional configuration + * to this {@link ClientCacheFactoryBean} on Spring container initialization. + * + * @return the Composite {@link ClientCacheConfigurer}. + * @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer + */ + public ClientCacheConfigurer getCompositeClientCacheConfigurer() { + return this.compositeClientCacheConfigurer; + } + /** * Set the GemFire System property 'durable-client-id' to indicate to the server that this client is durable. * @@ -405,7 +516,7 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat * @return a String value indicating the durable client id. */ public String getDurableClientId() { - return durableClientId; + return this.durableClientId; } /** @@ -427,13 +538,13 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat * the durable client's queue around. */ public Integer getDurableClientTimeout() { - return durableClientTimeout; + return this.durableClientTimeout; } /* (non-Javadoc) */ @Override - public final void setEnableAutoReconnect(final Boolean enableAutoReconnect) { - throw new UnsupportedOperationException("Auto-reconnect does not apply to clients."); + public final void setEnableAutoReconnect(Boolean enableAutoReconnect) { + throw new UnsupportedOperationException("Auto-reconnect does not apply to clients"); } /* (non-Javadoc) */ @@ -566,7 +677,7 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat * to the GemFire cluster. */ public Pool getPool() { - return pool; + return this.pool; } /** @@ -772,7 +883,7 @@ public class ClientCacheFactoryBean extends CacheFactoryBean implements Applicat /* (non-Javadoc) */ @Override public final void setUseClusterConfiguration(Boolean useClusterConfiguration) { - throw new UnsupportedOperationException("Shared, cluster-based configuration is not applicable for clients."); + throw new UnsupportedOperationException("Cluster-based Configuration is not applicable for clients"); } /* (non-Javadoc) */ diff --git a/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java index 1cf137f5..db5c44eb 100644 --- a/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/client/ClientRegionFactoryBean.java @@ -16,10 +16,18 @@ package org.springframework.data.gemfire.client; +import static java.util.Arrays.stream; import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.StreamSupport; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.geode.cache.CacheListener; import org.apache.geode.cache.CacheLoader; import org.apache.geode.cache.CacheWriter; @@ -33,28 +41,22 @@ import org.apache.geode.cache.client.ClientRegionFactory; import org.apache.geode.cache.client.ClientRegionShortcut; import org.apache.geode.cache.client.Pool; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; -import org.springframework.core.io.Resource; import org.springframework.data.gemfire.DataPolicyConverter; import org.springframework.data.gemfire.GemfireUtils; import org.springframework.data.gemfire.RegionLookupFactoryBean; +import org.springframework.data.gemfire.config.annotation.RegionConfigurer; import org.springframework.data.gemfire.config.xml.GemfireConstants; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * Spring {@link FactoryBean} used to create a GemFire client cache {@link Region}. + * Spring {@link FactoryBean} used to construct, configure and initialize a client {@link Region}. * * @author Costin Leau * @author David Turanski * @author John Blum - * @see org.springframework.beans.factory.BeanFactory - * @see org.springframework.beans.factory.BeanFactoryAware - * @see org.springframework.beans.factory.DisposableBean - * @see org.springframework.data.gemfire.RegionLookupFactoryBean * @see org.apache.geode.cache.DataPolicy * @see org.apache.geode.cache.EvictionAttributes * @see org.apache.geode.cache.GemFireCache @@ -64,18 +66,18 @@ import org.springframework.util.StringUtils; * @see org.apache.geode.cache.client.ClientRegionFactory * @see org.apache.geode.cache.client.ClientRegionShortcut * @see org.apache.geode.cache.client.Pool + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.data.gemfire.DataPolicyConverter + * @see org.springframework.data.gemfire.RegionLookupFactoryBean + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer */ @SuppressWarnings("unused") -public class ClientRegionFactoryBean extends RegionLookupFactoryBean - implements BeanFactoryAware, DisposableBean { - - private static final Log log = LogFactory.getLog(ClientRegionFactoryBean.class); +public class ClientRegionFactoryBean extends RegionLookupFactoryBean implements DisposableBean { private boolean close = false; private boolean destroy = false; - private BeanFactory beanFactory; - private Boolean persistent; private CacheListener[] cacheListeners; @@ -87,7 +89,7 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean private Class keyConstraint; private Class valueConstraint; - private ClientRegionShortcut shortcut = null; + private ClientRegionShortcut shortcut; private DataPolicy dataPolicy; @@ -95,97 +97,183 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean private Interest[] interests; + private List regionConfigurers = Collections.emptyList(); + private RegionAttributes attributes; - private Resource snapshot; + private RegionConfigurer compositeRegionConfigurer = new RegionConfigurer() { + + @Override + public void configure(String beanName, ClientRegionFactoryBean bean) { + nullSafeCollection(regionConfigurers) + .forEach(regionConfigurer -> regionConfigurer.configure(beanName, bean)); + } + }; private String diskStoreName; private String poolName; /** - * @inheritDoc + * Creates a new {@link Region} with the given {@link String name}. + * + * @param gemfireCache reference to the {@link GemFireCache}. + * @param regionName {@link String name} of the new {@link Region}. + * @return a new {@link Region} with the given {@link String name}. + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region */ @Override - public void afterPropertiesSet() throws Exception { - super.afterPropertiesSet(); - postProcess(getRegion()); - } + protected Region createRegion(GemFireCache gemfireCache, String regionName) throws Exception { - /** - * @inheritDoc - */ - @Override - protected Region lookupRegion(GemFireCache cache, String regionName) throws Exception { - Assert.isTrue(GemfireUtils.isClient(cache), "A ClientCache is required to create a client Region"); + applyRegionConfigurers(regionName); + + ClientCache cache = resolveCache(gemfireCache); ClientRegionFactory clientRegionFactory = - ((ClientCache) cache).createClientRegionFactory(resolveClientRegionShortcut()); + configure(createClientRegionFactory(cache, resolveClientRegionShortcut())); - setAttributes(clientRegionFactory); - addCacheListeners(clientRegionFactory); - setDiskStoreName(clientRegionFactory); - setEvictionAttributes(clientRegionFactory); - setPoolName(clientRegionFactory); - - if (keyConstraint != null) { - clientRegionFactory.setKeyConstraint(keyConstraint); - } - - if (valueConstraint != null) { - clientRegionFactory.setValueConstraint(valueConstraint); - } - - return logCreateRegionEvent(create(clientRegionFactory, regionName)); - } - - /* (non-Javadoc) */ - private Region create(ClientRegionFactory clientRegionFactory, String regionName) { - return (getParent() != null ? clientRegionFactory.createSubregion(getParent(), regionName) - : clientRegionFactory.create(regionName)); - } - - /* (non-Javadoc) */ - private Region logCreateRegionEvent(Region region) { - if (log.isInfoEnabled()) { - if (getParent() != null) { - log.info(String.format("Created new client cache Sub-Region [%1$s] under parent Region [%2$s].", - region.getName(), getParent().getName())); - } - else { - log.info(String.format("Created new client cache Region [%s].", region.getName())); - } - } + @SuppressWarnings("all") + Region region = newRegion(clientRegionFactory, getParent(), regionName); return region; } + /* (non-Javadoc) */ + private void applyRegionConfigurers(String regionName) { + applyRegionConfigurers(regionName, getCompositeRegionConfigurer()); + } + /** - * Resolves the {@link ClientRegionShortcut} used to configure the data policy of the client {@link Region}. + * Null-safe operation to apply the given array of {@link RegionConfigurer RegionConfigurers} + * to this {@link ClientRegionFactoryBean}. * - * @return a {@link ClientRegionShortcut} used to configure the data policy of the client {@link Region}. + * @param regionName {@link String} containing the name of the {@link Region}. + * @param regionConfigurers array of {@link RegionConfigurer RegionConfigurers} applied + * to this {@link ClientRegionFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + * @see #applyRegionConfigurers(String, Iterable) + */ + protected void applyRegionConfigurers(String regionName, RegionConfigurer... regionConfigurers) { + applyRegionConfigurers(regionName, Arrays.asList(nullSafeArray(regionConfigurers, RegionConfigurer.class))); + } + + /** + * Null-safe operation to apply the given {@link Iterable} of {@link RegionConfigurer RegionConfigurers} + * to this {@link ClientRegionFactoryBean}. + * + * @param regionName {@link String} containing the name of the {@link Region}. + * @param regionConfigurers {@link Iterable} of {@link RegionConfigurer RegionConfigurers} applied + * to this {@link ClientRegionFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + */ + protected void applyRegionConfigurers(String regionName, Iterable regionConfigurers) { + StreamSupport.stream(nullSafeIterable(regionConfigurers).spliterator(), false) + .forEach(regionConfigurer -> regionConfigurer.configure(regionName, this)); + } + + /** + * Assert the settings for {@link ClientRegionShortcut} and the {@literal persistent} attribute + * in <gfe:*-region> elements are compatible. + * + * @param resolvedShortcut {@link ClientRegionShortcut} resolved from the SDG XML namespace. + * @see org.springframework.data.gemfire.client.ClientRegionShortcutWrapper * @see org.apache.geode.cache.client.ClientRegionShortcut + * @see #isNotPersistent() + * @see #isPersistent() + */ + private void assertClientRegionShortcutAndPersistentAttributeAreCompatible(ClientRegionShortcut resolvedShortcut) { + + final boolean persistentNotSpecified = (this.persistent == null); + + if (ClientRegionShortcutWrapper.valueOf(resolvedShortcut).isPersistent()) { + Assert.isTrue(persistentNotSpecified || isPersistent(), + String.format("Client Region Shortcut [%s] is not valid when persistent is false", resolvedShortcut)); + } + else { + Assert.isTrue(persistentNotSpecified || isNotPersistent(), + String.format("Client Region Shortcut [%s] is not valid when persistent is true", resolvedShortcut)); + } + } + + /** + * Assert the settings for {@link DataPolicy} and the persistent attribute + * in <gfe:*-region> elements are compatible. + * + * @param resolvedDataPolicy {@link DataPolicy} resolved from the SDG XML namespace. + * @see org.apache.geode.cache.DataPolicy + * @see #isNotPersistent() + * @see #isPersistent() + */ + private void assertDataPolicyAndPersistentAttributeAreCompatible(DataPolicy resolvedDataPolicy) { + + if (resolvedDataPolicy.withPersistence()) { + Assert.isTrue(isPersistentUnspecified() || isPersistent(), + String.format("Data Policy [%s] is not valid when persistent is false", resolvedDataPolicy)); + } + else { + Assert.isTrue(isPersistentUnspecified() || isNotPersistent(), + String.format("Data Policy [%s] is not valid when persistent is true", resolvedDataPolicy)); + } + } + + /* (non-Javadoc) */ + private Region newRegion(ClientRegionFactory clientRegionFactory, + Region parentRegion, String regionName) { + + return Optional.ofNullable(parentRegion) + .map(parent -> { + logInfo("Creating client Subregion [%1$s] with parent Region [%2$s]", + regionName, parent.getName()); + + return clientRegionFactory.createSubregion(parent, regionName); + }) + .orElseGet(() -> { + logInfo("Created client Region [%s]", regionName); + + return clientRegionFactory.create(regionName); + }); + } + + /* (non-Javadoc) */ + private ClientCache resolveCache(GemFireCache gemfireCache) { + + return Optional.ofNullable(gemfireCache) + .filter(GemfireUtils::isClient) + .map(cache -> (ClientCache) cache) + .orElseThrow(() -> newIllegalArgumentException("ClientCache is required")); + } + + /** + * Resolves the {@link ClientRegionShortcut} used to configure the {@link DataPolicy} of the client {@link Region}. + * + * @return a {@link ClientRegionShortcut} used to configure the {@link DataPolicy} of the client {@link Region}. + * @see org.apache.geode.cache.client.ClientRegionShortcut + * @see org.apache.geode.cache.DataPolicy */ ClientRegionShortcut resolveClientRegionShortcut() { ClientRegionShortcut resolvedShortcut = this.shortcut; if (resolvedShortcut == null) { - if (this.dataPolicy != null) { - assertDataPolicyAndPersistentAttributeAreCompatible(this.dataPolicy); - if (DataPolicy.EMPTY.equals(this.dataPolicy)) { + DataPolicy dataPolicy = this.dataPolicy; + + if (dataPolicy != null) { + + assertDataPolicyAndPersistentAttributeAreCompatible(dataPolicy); + + if (DataPolicy.EMPTY.equals(dataPolicy)) { resolvedShortcut = ClientRegionShortcut.PROXY; } - else if (DataPolicy.NORMAL.equals(this.dataPolicy)) { + else if (DataPolicy.NORMAL.equals(dataPolicy)) { resolvedShortcut = ClientRegionShortcut.CACHING_PROXY; } - else if (DataPolicy.PERSISTENT_REPLICATE.equals(this.dataPolicy)) { + else if (DataPolicy.PERSISTENT_REPLICATE.equals(dataPolicy)) { resolvedShortcut = ClientRegionShortcut.LOCAL_PERSISTENT; } else { // NOTE the DataPolicy validation is based on the ClientRegionShortcut initialization logic // in org.apache.geode.internal.cache.GemFireCacheImpl.initializeClientRegionShortcuts - throw new IllegalArgumentException(String.format("Data Policy '%s' is invalid for Client Regions", - this.dataPolicy)); + throw newIllegalArgumentException("Data Policy [%s] is not valid for the client Region", dataPolicy); } } else { @@ -194,145 +282,20 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean } } - // NOTE the ClientRegionShortcut and Persistent attribute will be compatible if the shortcut - // was derived from the Data Policy. + // NOTE the ClientRegionShortcut and Persistent attribute will be compatible + // if the shortcut was derived from the Data Policy. assertClientRegionShortcutAndPersistentAttributeAreCompatible(resolvedShortcut); return resolvedShortcut; } - /** - * Validates that the settings for ClientRegionShortcut and the 'persistent' attribute in <gfe:*-region> elements - * are compatible. - * - * @param resolvedShortcut the GemFire ClientRegionShortcut resolved form the Spring GemFire XML namespace - * configuration meta-data. - * @see #isPersistent() - * @see #isNotPersistent() - * @see org.apache.geode.cache.client.ClientRegionShortcut - */ - private void assertClientRegionShortcutAndPersistentAttributeAreCompatible(ClientRegionShortcut resolvedShortcut) { - final boolean persistentNotSpecified = (this.persistent == null); - - if (ClientRegionShortcut.LOCAL_PERSISTENT.equals(resolvedShortcut) - || ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW.equals(resolvedShortcut)) { - Assert.isTrue(persistentNotSpecified || isPersistent(), String.format( - "Client Region Shortcut '%s' is invalid when persistent is false", resolvedShortcut)); - } - else { - Assert.isTrue(persistentNotSpecified || isNotPersistent(), String.format( - "Client Region Shortcut '%s' is invalid when persistent is true", resolvedShortcut)); - } - } - - /** - * Validates that the settings for Data Policy and the 'persistent' attribute in <gfe:*-region> elements - * are compatible. - * - * @param resolvedDataPolicy the GemFire Data Policy resolved form the Spring GemFire XML namespace configuration - * meta-data. - * @see #isPersistent() - * @see #isNotPersistent() - * @see org.apache.geode.cache.DataPolicy - */ - private void assertDataPolicyAndPersistentAttributeAreCompatible(DataPolicy resolvedDataPolicy) { - if (resolvedDataPolicy.withPersistence()) { - Assert.isTrue(isPersistentUnspecified() || isPersistent(), String.format( - "Data Policy '%s' is invalid when persistent is false", resolvedDataPolicy)); - } - else { - // NOTE otherwise, the Data Policy is without persistence, so... - Assert.isTrue(isPersistentUnspecified() || isNotPersistent(), String.format( - "Data Policy '%s' is invalid when persistent is true", resolvedDataPolicy)); - } - } - - /* (non-Javadoc) */ - private ClientRegionFactory setAttributes(ClientRegionFactory clientRegionFactory) { - RegionAttributes localAttributes = this.attributes; - - if (localAttributes != null) { - clientRegionFactory.setCloningEnabled(localAttributes.getCloningEnabled()); - clientRegionFactory.setCompressor(localAttributes.getCompressor()); - clientRegionFactory.setConcurrencyChecksEnabled(localAttributes.getConcurrencyChecksEnabled()); - clientRegionFactory.setConcurrencyLevel(localAttributes.getConcurrencyLevel()); - clientRegionFactory.setCustomEntryIdleTimeout(localAttributes.getCustomEntryIdleTimeout()); - clientRegionFactory.setCustomEntryTimeToLive(localAttributes.getCustomEntryTimeToLive()); - clientRegionFactory.setDiskStoreName(localAttributes.getDiskStoreName()); - clientRegionFactory.setDiskSynchronous(localAttributes.isDiskSynchronous()); - clientRegionFactory.setEntryIdleTimeout(localAttributes.getEntryIdleTimeout()); - clientRegionFactory.setEntryTimeToLive(localAttributes.getEntryTimeToLive()); - clientRegionFactory.setEvictionAttributes(localAttributes.getEvictionAttributes()); - clientRegionFactory.setInitialCapacity(localAttributes.getInitialCapacity()); - clientRegionFactory.setKeyConstraint(localAttributes.getKeyConstraint()); - clientRegionFactory.setLoadFactor(localAttributes.getLoadFactor()); - clientRegionFactory.setPoolName(localAttributes.getPoolName()); - clientRegionFactory.setRegionIdleTimeout(localAttributes.getRegionIdleTimeout()); - clientRegionFactory.setRegionTimeToLive(localAttributes.getRegionTimeToLive()); - clientRegionFactory.setStatisticsEnabled(localAttributes.getStatisticsEnabled()); - clientRegionFactory.setValueConstraint(localAttributes.getValueConstraint()); - } - - return clientRegionFactory; - } - - /* (non-Javadoc) */ - @SuppressWarnings("unchecked") - private ClientRegionFactory addCacheListeners(ClientRegionFactory clientRegionFactory) { - for (CacheListener cacheListener : this.attributesCacheListeners()) { - clientRegionFactory.addCacheListener(cacheListener); - } - - for (CacheListener cacheListener : nullSafeArray(this.cacheListeners, CacheListener.class)) { - clientRegionFactory.addCacheListener(cacheListener); - } - - return clientRegionFactory; - } - - /* (non-Javadoc) */ - @SuppressWarnings("unchecked") - private CacheListener[] attributesCacheListeners() { - CacheListener[] cacheListeners = (this.attributes != null ? this.attributes.getCacheListeners() : null); - return nullSafeArray(cacheListeners, CacheListener.class); - } - - /* (non-Javadoc) */ - private ClientRegionFactory setDiskStoreName(ClientRegionFactory clientRegionFactory) { - if (StringUtils.hasText(this.diskStoreName)) { - clientRegionFactory.setDiskStoreName(this.diskStoreName); - } - - return clientRegionFactory; - } - - /* (non-Javadoc) */ - private ClientRegionFactory setEvictionAttributes(ClientRegionFactory clientRegionFactory) { - if (this.evictionAttributes != null) { - clientRegionFactory.setEvictionAttributes(this.evictionAttributes); - } - - return clientRegionFactory; - } - - /* (non-Javadoc) */ - private ClientRegionFactory setPoolName(ClientRegionFactory clientRegionFactory) { - String poolName = resolvePoolName(); - - if (StringUtils.hasText(poolName)) { - clientRegionFactory.setPoolName(eagerlyInitializePool(poolName)); - } - - return clientRegionFactory; - } - /* (non-Javadoc) */ private String resolvePoolName() { String poolName = this.poolName; if (!StringUtils.hasText(poolName)) { String defaultPoolName = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME; - poolName = (this.beanFactory.containsBean(defaultPoolName) ? defaultPoolName : poolName); + poolName = (getBeanFactory().containsBean(defaultPoolName) ? defaultPoolName : poolName); } return poolName; @@ -340,35 +303,117 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean /* (non-Javadoc) */ private String eagerlyInitializePool(String poolName) { - try { - if (this.beanFactory.isTypeMatch(poolName, Pool.class)) { - if (log.isDebugEnabled()) { - log.debug(String.format("Found bean definition for Pool [%1$s]; Eagerly initializing...", poolName)); - } - this.beanFactory.getBean(poolName, Pool.class); + try { + if (getBeanFactory().isTypeMatch(poolName, Pool.class)) { + logDebug("Found bean definition for Pool [%s]; Eagerly initializing...", poolName); + getBeanFactory().getBean(poolName, Pool.class); } } catch (BeansException ignore) { - log.warn(ignore.getMessage()); + getLog().warn(ignore.getMessage(), ignore.getCause()); } return poolName; } - /* (non-Javadoc) */ - protected void postProcess(Region region) throws Exception { - loadSnapshot(region); - registerInterests(region); - setCacheLoader(region); - setCacheWriter(region); + /** + * Constructs a new instance of {@link ClientRegionFactory} using the given {@link ClientCache} + * and {@link ClientRegionShortcut}. + * + * @param cache reference to the {@link ClientCache}. + * @param shortcut {@link ClientRegionShortcut} used to specify the client {@link Region} {@link DataPolicy}. + * @return a new instance of {@link ClientRegionFactory}. + * @see org.apache.geode.cache.client.ClientCache#createClientRegionFactory(ClientRegionShortcut) + * @see org.apache.geode.cache.client.ClientRegionShortcut + * @see org.apache.geode.cache.client.ClientRegionFactory + */ + protected ClientRegionFactory createClientRegionFactory(ClientCache cache, ClientRegionShortcut shortcut) { + return cache.createClientRegionFactory(shortcut); } - /* (non-Javadoc) */ - private Region loadSnapshot(Region region) throws Exception { - if (snapshot != null) { - region.loadSnapshot(snapshot.getInputStream()); - } + /** + * Configures the given {@link ClientRegionFactoryBean} from the configuration settings + * of this {@link ClientRegionFactoryBean}. + * + * @param clientRegionFactory {@link ClientRegionFactory} to configure. + * @return the given {@link ClientRegionFactory}. + * @see org.apache.geode.cache.client.ClientRegionFactory + */ + protected ClientRegionFactory configure(ClientRegionFactory clientRegionFactory) { + + Optional.ofNullable(this.attributes).ifPresent(attributes -> { + + stream(nullSafeArray(attributes.getCacheListeners(), CacheListener.class)) + .forEach(clientRegionFactory::addCacheListener); + + clientRegionFactory.setCloningEnabled(attributes.getCloningEnabled()); + clientRegionFactory.setCompressor(attributes.getCompressor()); + clientRegionFactory.setConcurrencyChecksEnabled(attributes.getConcurrencyChecksEnabled()); + clientRegionFactory.setConcurrencyLevel(attributes.getConcurrencyLevel()); + clientRegionFactory.setCustomEntryIdleTimeout(attributes.getCustomEntryIdleTimeout()); + clientRegionFactory.setCustomEntryTimeToLive(attributes.getCustomEntryTimeToLive()); + clientRegionFactory.setDiskStoreName(attributes.getDiskStoreName()); + clientRegionFactory.setDiskSynchronous(attributes.isDiskSynchronous()); + clientRegionFactory.setEntryIdleTimeout(attributes.getEntryIdleTimeout()); + clientRegionFactory.setEntryTimeToLive(attributes.getEntryTimeToLive()); + clientRegionFactory.setEvictionAttributes(attributes.getEvictionAttributes()); + clientRegionFactory.setInitialCapacity(attributes.getInitialCapacity()); + clientRegionFactory.setKeyConstraint(attributes.getKeyConstraint()); + clientRegionFactory.setLoadFactor(attributes.getLoadFactor()); + clientRegionFactory.setPoolName(attributes.getPoolName()); + clientRegionFactory.setRegionIdleTimeout(attributes.getRegionIdleTimeout()); + clientRegionFactory.setRegionTimeToLive(attributes.getRegionTimeToLive()); + clientRegionFactory.setStatisticsEnabled(attributes.getStatisticsEnabled()); + clientRegionFactory.setValueConstraint(attributes.getValueConstraint()); + }); + + stream(nullSafeArray(this.cacheListeners, CacheListener.class)).forEach(clientRegionFactory::addCacheListener); + + Optional.ofNullable(this.diskStoreName).filter(StringUtils::hasText) + .ifPresent(clientRegionFactory::setDiskStoreName); + + Optional.ofNullable(this.evictionAttributes).ifPresent(clientRegionFactory::setEvictionAttributes); + + Optional.ofNullable(this.keyConstraint).ifPresent(clientRegionFactory::setKeyConstraint); + + Optional.ofNullable(resolvePoolName()).filter(StringUtils::hasText) + .ifPresent(poolName -> clientRegionFactory.setPoolName(eagerlyInitializePool(poolName))); + + Optional.ofNullable(this.valueConstraint).ifPresent(clientRegionFactory::setValueConstraint); + + return clientRegionFactory; + } + + /** + * Post-process the given {@link ClientRegionFactory} setup by this {@link ClientRegionFactoryBean}. + * + * @param clientRegionFactory {@link ClientRegionFactory} to process. + * @return the given {@link ClientRegionFactory}. + * @see org.apache.geode.cache.client.ClientRegionFactory + */ + protected ClientRegionFactory postProcess(ClientRegionFactory clientRegionFactory) { + return clientRegionFactory; + } + + /** + * Post-process the {@link Region} created by this {@link ClientRegionFactoryBean}. + * + * @param region {@link Region} to process. + * @see org.apache.geode.cache.Region + */ + @Override + protected Region postProcess(Region region) { + + super.postProcess(region); + + registerInterests(region); + + Optional.ofNullable(this.cacheLoader) + .ifPresent(cacheLoader -> region.getAttributesMutator().setCacheLoader(cacheLoader)); + + Optional.ofNullable(this.cacheWriter) + .ifPresent(cacheWriter -> region.getAttributesMutator().setCacheWriter(cacheWriter)); return region; } @@ -376,47 +421,32 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean /* (non-Javadoc) */ @SuppressWarnings("unchecked") private Region registerInterests(Region region) { - for (Interest interest : nullSafeArray(interests, Interest.class)) { + + stream(nullSafeArray(this.interests, Interest.class)).forEach(interest -> { if (interest.isRegexType()) { region.registerInterestRegex((String) interest.getKey(), interest.getPolicy(), interest.isDurable(), interest.isReceiveValues()); } else { - region.registerInterest(interest.getKey(), interest.getPolicy(), interest.isDurable(), - interest.isReceiveValues()); + region.registerInterest(((Interest) interest).getKey(), interest.getPolicy(), + interest.isDurable(), interest.isReceiveValues()); } - } - - return region; - } - - /* (non-Javadoc) */ - private Region setCacheLoader(Region region) { - if (cacheLoader != null) { - region.getAttributesMutator().setCacheLoader(this.cacheLoader); - } - - return region; - } - - /* (non-Javadoc) */ - private Region setCacheWriter(Region region) { - if (cacheWriter != null) { - region.getAttributesMutator().setCacheWriter(this.cacheWriter); - } + }); return region; } /** - * @inheritDoc + * Closes and destroys the {@link Region}. + * + * @throws Exception if destroy fails. + * @see org.springframework.beans.factory.DisposableBean */ @Override public void destroy() throws Exception { - Region region = getObject(); - if (region != null) { - if (close) { + Optional.ofNullable(getObject()).ifPresent(region -> { + if (isClose()) { if (!region.getRegionService().isClosed()) { try { region.close(); @@ -426,10 +456,21 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean } } - if (destroy) { + if (isDestroy()) { region.destroyRegion(); } - } + }); + } + + /** + * Returns a reference to the Composite {@link RegionConfigurer} used to apply additional configuration + * to this {@link ClientRegionFactoryBean} on Spring container initialization. + * + * @return the Composite {@link RegionConfigurer}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + */ + protected RegionConfigurer getCompositeRegionConfigurer() { + return this.compositeRegionConfigurer; } /** @@ -446,29 +487,6 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean this.attributes = attributes; } - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - /* (non-Javadoc) */ - final boolean isClose() { - return close; - } - - /** - * Indicates whether the region referred by this factory bean, will be - * closed on shutdown (default true). Note: destroy and close are mutually - * exclusive. Enabling one will automatically disable the other. - * - * @param close whether to close or not the region - * @see #setDestroy(boolean) - */ - public void setClose(boolean close) { - this.close = close; - this.destroy = (this.destroy && !close); // retain previous value iff close is false. - } - /** * Sets the cache listeners used for the region used by this factory. Used * only when a new region is created.Overrides the settings specified @@ -500,6 +518,24 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean this.cacheWriter = cacheWriter; } + /* (non-Javadoc) */ + final boolean isClose() { + return this.close; + } + + /** + * Indicates whether the region referred by this factory bean will be closed on shutdown (default true). + * + * Note: destroy and close are mutually exclusive. Enabling one will automatically disable the other. + * + * @param close whether to close or not the region + * @see #setDestroy(boolean) + */ + public void setClose(boolean close) { + this.close = close; + this.destroy = (this.destroy && !close); // retain previous value iff close is false. + } + /** * Sets the Data Policy. Used only when a new Region is created. * @@ -521,13 +557,13 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean @Deprecated public void setDataPolicyName(String dataPolicyName) { DataPolicy resolvedDataPolicy = new DataPolicyConverter().convert(dataPolicyName); - Assert.notNull(resolvedDataPolicy, String.format("Data Policy '%1$s' is invalid.", dataPolicyName)); + Assert.notNull(resolvedDataPolicy, String.format("Data Policy [%1$s] is not valid", dataPolicyName)); setDataPolicy(resolvedDataPolicy); } /* (non-Javadoc) */ final boolean isDestroy() { - return destroy; + return this.destroy; } /** @@ -598,8 +634,8 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean * @see org.apache.geode.cache.client.Pool */ public void setPool(Pool pool) { - Assert.notNull(pool, "Pool cannot be null"); - setPoolName(pool.getName()); + setPoolName(Optional.ofNullable(pool).map(Pool::getName) + .orElseThrow(() -> newIllegalArgumentException("Pool cannot be null"))); } /** @@ -608,8 +644,33 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean * @param poolName String specifying the name of the GemFire client {@link Pool}. */ public void setPoolName(String poolName) { - Assert.hasText(poolName, "Pool name is required"); - this.poolName = poolName; + this.poolName = Optional.ofNullable(poolName).filter(StringUtils::hasText) + .orElseThrow(() -> newIllegalArgumentException("Pool name is required")); + } + + /** + * Null-safe operation to set an array of {@link RegionConfigurer RegionConfigurers} used to apply + * additional configuration to this {@link ClientRegionFactoryBean} when using Annotation-based configuration. + * + * @param regionConfigurers array of {@link RegionConfigurer RegionConfigurers} used to apply + * additional configuration to this {@link ClientRegionFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + * @see #setRegionConfigurers(List) + */ + public void setRegionConfigurers(RegionConfigurer... regionConfigurers) { + setRegionConfigurers(Arrays.asList(nullSafeArray(regionConfigurers, RegionConfigurer.class))); + } + + /** + * Null-safe operation to set an {@link Iterable} of {@link RegionConfigurer RegionConfigurers} used to apply + * additional configuration to this {@link ClientRegionFactoryBean} when using Annotation-based configuration. + * + * @param regionConfigurers {@link Iterable} of {@link RegionConfigurer RegionConfigurers} used to apply + * additional configuration to this {@link ClientRegionFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + */ + public void setRegionConfigurers(List regionConfigurers) { + this.regionConfigurers = Optional.ofNullable(regionConfigurers).orElseGet(Collections::emptyList); } /** @@ -621,18 +682,6 @@ public class ClientRegionFactoryBean extends RegionLookupFactoryBean this.shortcut = shortcut; } - /** - * Specifies the data snapshots used for loading a newly created {@link Region}. - * The snapshot will be used only when a new {@link Region} is created. - * If the {@link Region} already exists, no loading will be performed. - * - * @param snapshot {@link Resource} referencing the snapshot used to load the {@link Region} with data. - * @see org.springframework.core.io.Resource - */ - public void setSnapshot(Resource snapshot) { - this.snapshot = snapshot; - } - public void setValueConstraint(Class valueConstraint) { this.valueConstraint = valueConstraint; } diff --git a/src/main/java/org/springframework/data/gemfire/client/ClientRegionShortcutWrapper.java b/src/main/java/org/springframework/data/gemfire/client/ClientRegionShortcutWrapper.java index 20eb3e22..9a1fe03f 100644 --- a/src/main/java/org/springframework/data/gemfire/client/ClientRegionShortcutWrapper.java +++ b/src/main/java/org/springframework/data/gemfire/client/ClientRegionShortcutWrapper.java @@ -31,6 +31,7 @@ import org.springframework.util.ObjectUtils; */ @SuppressWarnings("unused") public enum ClientRegionShortcutWrapper { + CACHING_PROXY(ClientRegionShortcut.CACHING_PROXY, DataPolicy.NORMAL), CACHING_PROXY_HEAP_LRU(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU, DataPolicy.NORMAL), CACHING_PROXY_OVERFLOW(ClientRegionShortcut.CACHING_PROXY_OVERFLOW, DataPolicy.NORMAL), @@ -46,11 +47,6 @@ public enum ClientRegionShortcutWrapper { private final DataPolicy dataPolicy; - ClientRegionShortcutWrapper(ClientRegionShortcut clientRegionShortcut, DataPolicy dataPolicy) { - this.clientRegionShortcut = clientRegionShortcut; - this.dataPolicy = dataPolicy; - } - public static ClientRegionShortcutWrapper valueOf(ClientRegionShortcut clientRegionShortcut) { for (ClientRegionShortcutWrapper wrapper : values()) { if (ObjectUtils.nullSafeEquals(wrapper.getClientRegionShortcut(), clientRegionShortcut)) { @@ -61,6 +57,11 @@ public enum ClientRegionShortcutWrapper { return ClientRegionShortcutWrapper.UNSPECIFIED; } + ClientRegionShortcutWrapper(ClientRegionShortcut clientRegionShortcut, DataPolicy dataPolicy) { + this.clientRegionShortcut = clientRegionShortcut; + this.dataPolicy = dataPolicy; + } + public ClientRegionShortcut getClientRegionShortcut() { return this.clientRegionShortcut; } diff --git a/src/main/java/org/springframework/data/gemfire/client/PoolFactoryBean.java b/src/main/java/org/springframework/data/gemfire/client/PoolFactoryBean.java index 5602f1c9..c79fb8ec 100644 --- a/src/main/java/org/springframework/data/gemfire/client/PoolFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/client/PoolFactoryBean.java @@ -16,74 +16,68 @@ package org.springframework.data.gemfire.client; +import static java.util.stream.StreamSupport.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; + import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.client.Pool; import org.apache.geode.cache.client.PoolFactory; import org.apache.geode.cache.client.PoolManager; import org.apache.geode.cache.query.QueryService; import org.apache.geode.distributed.DistributedSystem; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.data.gemfire.GemfireUtils; +import org.springframework.data.gemfire.config.annotation.PoolConfigurer; +import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport; import org.springframework.data.gemfire.support.ConnectionEndpoint; import org.springframework.data.gemfire.support.ConnectionEndpointList; import org.springframework.data.gemfire.util.DistributedSystemUtils; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * FactoryBean for easy declaration and configuration of a GemFire {@link Pool}. If a new {@link Pool} is created, - * its lifecycle is bound to that of this declaring factory. + * Spring {@link FactoryBean} to construct, configure and initialize a {@link Pool}. * - * Note, if a {@link Pool} having the configured name already exists, then the existing {@link Pool} will be returned - * as is without any modifications and its lifecycle will be unaffected by this factory. + * If a new {@link Pool} is created, its lifecycle is bound to that of this declaring {@link FactoryBean} + * and indirectly, the Spring container. + * + * If a {@link Pool} having the configured {@link String name} already exists, then the existing {@link Pool} + * will be returned as is without any modifications and its lifecycle will be unaffected by this {@link FactoryBean}. * * @author Costin Leau * @author John Blum * @see java.net.InetSocketAddress - * @see org.springframework.beans.factory.BeanNameAware - * @see org.springframework.beans.factory.DisposableBean - * @see org.springframework.beans.factory.FactoryBean - * @see org.springframework.beans.factory.InitializingBean - * @see org.springframework.data.gemfire.support.ConnectionEndpoint - * @see org.springframework.data.gemfire.support.ConnectionEndpointList + * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.client.Pool * @see org.apache.geode.cache.client.PoolFactory * @see org.apache.geode.cache.client.PoolManager + * @see org.apache.geode.distributed.DistributedSystem + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.beans.factory.InitializingBean + * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer + * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport + * @see org.springframework.data.gemfire.support.ConnectionEndpoint + * @see org.springframework.data.gemfire.support.ConnectionEndpointList */ @SuppressWarnings("unused") -public class PoolFactoryBean implements FactoryBean, InitializingBean, DisposableBean, - BeanNameAware, BeanFactoryAware { +public class PoolFactoryBean extends AbstractFactoryBeanSupport implements DisposableBean, InitializingBean { protected static final int DEFAULT_LOCATOR_PORT = DistributedSystemUtils.DEFAULT_LOCATOR_PORT; protected static final int DEFAULT_SERVER_PORT = DistributedSystemUtils.DEFAULT_CACHE_SERVER_PORT; - private static final Log log = LogFactory.getLog(PoolFactoryBean.class); - // indicates whether the Pool has been created internally (by this FactoryBean) or not volatile boolean springBasedPool = true; - private BeanFactory beanFactory; - - private ConnectionEndpointList locators = new ConnectionEndpointList(); - private ConnectionEndpointList servers = new ConnectionEndpointList(); - - private volatile Pool pool; - - private String beanName; - private String name; - // GemFire Pool Configuration Settings private boolean keepAlive = false; private boolean multiUserAuthentication = PoolFactory.DEFAULT_MULTIUSER_AUTHENTICATION; @@ -106,109 +100,150 @@ public class PoolFactoryBean implements FactoryBean, InitializingBean, Dis private long idleTimeout = PoolFactory.DEFAULT_IDLE_TIMEOUT; private long pingInterval = PoolFactory.DEFAULT_PING_INTERVAL; + private ConnectionEndpointList locators = new ConnectionEndpointList(); + private ConnectionEndpointList servers = new ConnectionEndpointList(); + + private List poolConfigurers = Collections.emptyList(); + + private volatile Pool pool; + + private PoolConfigurer compositePoolConfigurer = (beanName, bean) -> + nullSafeCollection(poolConfigurers).forEach(poolConfigurer -> poolConfigurer.configure(beanName, bean)); + + private String name; private String serverGroup = PoolFactory.DEFAULT_SERVER_GROUP; /** - * Constructs and initializes a GemFire {@link Pool}. + * Prepares the construction, configuration and initialization of a new {@link Pool}. * - * @throws Exception if the {@link Pool} creation and initialization fails. - * @see org.apache.geode.cache.client.Pool - * @see org.apache.geode.cache.client.PoolFactory + * @throws Exception if {@link Pool} initialization fails. * @see org.apache.geode.cache.client.PoolManager - * @see #createPoolFactory() + * @see org.apache.geode.cache.client.PoolFactory + * @see org.apache.geode.cache.client.Pool */ @Override public void afterPropertiesSet() throws Exception { - if (!StringUtils.hasText(name)) { - Assert.hasText(beanName, "Pool 'name' is required"); - this.name = beanName; - } - - // check for an existing, configured Pool with name first - Pool existingPool = PoolManager.find(name); - - if (existingPool != null) { - if (log.isDebugEnabled()) { - log.debug(String.format("A Pool with name [%1$s] already exists; using existing Pool.", name)); - } - - this.springBasedPool = false; - this.pool = existingPool; - } - else { - if (log.isDebugEnabled()) { - log.debug(String.format("No Pool with name [%1$s] was found. Creating new Pool.", name)); - } - - this.springBasedPool = true; - } + init(Optional.ofNullable(PoolManager.find(validatePoolName()))); } - /** - * Destroys the GemFire {@link Pool} if created by this {@link PoolFactoryBean} and releases all system resources - * used by the {@link Pool}. - * - * @throws Exception if the {@link Pool} destruction caused an error. - * @see DisposableBean#destroy() - */ - @Override - public void destroy() throws Exception { - if (springBasedPool && pool != null && !pool.isDestroyed()) { - pool.releaseThreadLocalConnection(); - pool.destroy(keepAlive); - pool = null; + /* (non-Javadoc) */ + @SuppressWarnings("all") + private void init(Optional existingPool) { - if (log.isDebugEnabled()) { - log.debug(String.format("Destroyed Pool [%1$s]", name)); - } + if (existingPool.isPresent()) { + this.pool = existingPool.get(); + this.springBasedPool = false; + + logDebug(() -> String.format( + "Pool with name [%s] already exists; Using existing Pool; Pool Configurers [%d] will not be applied", + existingPool.get().getName(), this.poolConfigurers.size())); + } + else { + this.springBasedPool = true; + applyPoolConfigurers(); + + logDebug("No Pool with name [%s] was found; Creating new Pool", getName()); } } /* (non-Javadoc) */ - @Override - public Pool getObject() throws Exception { - if (this.pool == null) { - eagerlyInitializeClientCacheIfNotPresent(); - - PoolFactory poolFactory = createPoolFactory(); - - poolFactory.setFreeConnectionTimeout(freeConnectionTimeout); - poolFactory.setIdleTimeout(idleTimeout); - poolFactory.setLoadConditioningInterval(loadConditioningInterval); - poolFactory.setMaxConnections(maxConnections); - poolFactory.setMinConnections(minConnections); - poolFactory.setMultiuserAuthentication(multiUserAuthentication); - poolFactory.setPingInterval(pingInterval); - poolFactory.setPRSingleHopEnabled(prSingleHopEnabled); - poolFactory.setReadTimeout(readTimeout); - poolFactory.setRetryAttempts(retryAttempts); - poolFactory.setServerGroup(serverGroup); - poolFactory.setSocketBufferSize(socketBufferSize); - poolFactory.setStatisticInterval(statisticInterval); - poolFactory.setSubscriptionAckInterval(subscriptionAckInterval); - poolFactory.setSubscriptionEnabled(subscriptionEnabled); - poolFactory.setSubscriptionMessageTrackingTimeout(subscriptionMessageTrackingTimeout); - poolFactory.setSubscriptionRedundancy(subscriptionRedundancy); - poolFactory.setThreadLocalConnections(threadLocalConnections); - - for (ConnectionEndpoint locator : this.locators) { - poolFactory.addLocator(locator.getHost(), locator.getPort()); - } - - for (ConnectionEndpoint server : this.servers) { - poolFactory.addServer(server.getHost(), server.getPort()); - } - - pool = poolFactory.create(name); - } - - return pool; + private void applyPoolConfigurers() { + applyPoolConfigurers(getCompositePoolConfigurer()); } /** - * Determines whether the GemFire DistributedSystem exists yet or not. + * Null-safe operation to apply the given array of {@link PoolConfigurer PoolConfigurers} + * to this {@link PoolFactoryBean}. * - * @return a boolean value indicating whether the single, GemFire DistributedSystem has been created already. + * @param poolConfigurers array of {@link PoolConfigurer PoolConfigurers} applied to this {@link PoolFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer + * @see #applyPoolConfigurers(Iterable) + */ + protected void applyPoolConfigurers(PoolConfigurer... poolConfigurers) { + applyPoolConfigurers(Arrays.asList(nullSafeArray(poolConfigurers, PoolConfigurer.class))); + } + + /** + * Null-safe operation to apply the given {@link Iterable} of {@link PoolConfigurer PoolConfigurers} + * to this {@link PoolFactoryBean}. + * + * @param poolConfigurers {@link Iterable} of {@link PoolConfigurer PoolConfigurers} + * applied to this {@link PoolFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer + */ + protected void applyPoolConfigurers(Iterable poolConfigurers) { + stream(nullSafeIterable(poolConfigurers).spliterator(), false) + .forEach(poolConfigurer -> poolConfigurer.configure(getName(), this)); + } + + /* (non-Javadoc) */ + private String validatePoolName() { + + if (!StringUtils.hasText(getName())) { + setName(Optional.ofNullable(getBeanName()).filter(StringUtils::hasText) + .orElseThrow(() -> newIllegalArgumentException("Pool name is required"))); + } + + return getName(); + } + + /** + * Releases all system resources and destroys the {@link Pool} when created by this {@link PoolFactoryBean}. + * + * @throws Exception if the {@link Pool} destruction caused an error. + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + @Override + public void destroy() throws Exception { + + Optional.ofNullable(this.pool) + .filter(pool -> this.springBasedPool) + .filter(pool -> !pool.isDestroyed()) + .ifPresent(pool -> { + pool.releaseThreadLocalConnection(); + pool.destroy(this.keepAlive); + setPool(null); + logDebug("Destroyed Pool [%s]", pool.getName()); + }); + } + + /** + * Returns a reference to the Composite {@link PoolConfigurer} used to apply additional configuration + * to this {@link PoolFactoryBean} on Spring container initialization. + * + * @return the Composite {@link PoolConfigurer}. + * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer + */ + protected PoolConfigurer getCompositePoolConfigurer() { + return this.compositePoolConfigurer; + } + + /** + * Returns an object reference to the {@link Pool} created by this {@link PoolFactoryBean}. + * + * @return an object reference to the {@link Pool} created by this {@link PoolFactoryBean}. + * @see org.springframework.beans.factory.FactoryBean#getObject() + * @see org.apache.geode.cache.client.Pool + */ + @Override + public Pool getObject() throws Exception { + + return Optional.ofNullable(this.pool).orElseGet(() -> { + + eagerlyInitializeClientCacheIfNotPresent(); + + PoolFactory poolFactory = configure(createPoolFactory()); + + this.pool = create(poolFactory, getName()); + + return this.pool; + }); + } + + /** + * Determines whether the {@link DistributedSystem} exists yet or not. + * + * @return a boolean value indicating whether the single, {@link DistributedSystem} has already been created. * @see org.springframework.data.gemfire.GemfireUtils#getDistributedSystem() * @see org.springframework.data.gemfire.GemfireUtils#isConnected(DistributedSystem) * @see org.apache.geode.distributed.DistributedSystem @@ -218,22 +253,22 @@ public class PoolFactoryBean implements FactoryBean, InitializingBean, Dis } /** - * Attempts to eagerly initialize the GemFire {@link ClientCache} if not already present so that the single - * {@link org.apache.geode.distributed.DistributedSystem} will exists, which is required to create - * a {@link Pool} instance. + * Attempts to eagerly initialize the {@link ClientCache} if not already present so that a single + * {@link DistributedSystem} will exist, which is required to create a {@link Pool} instance. * * @see org.springframework.beans.factory.BeanFactory#getBean(Class) * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.distributed.DistributedSystem + * @see #isDistributedSystemPresent() */ - void eagerlyInitializeClientCacheIfNotPresent() { + private void eagerlyInitializeClientCacheIfNotPresent() { if (!isDistributedSystemPresent()) { getBeanFactory().getBean(ClientCache.class); } } /** - * Creates an instance of the GemFire {@link PoolFactory} interface to construct, configure and initialize - * a GemFire {@link Pool}. + * Creates an instance of the {@link PoolFactory} interface to construct, configure and initialize a {@link Pool}. * * @return a {@link PoolFactory} implementation to create a {@link Pool}. * @see org.apache.geode.cache.client.PoolManager#createFactory() @@ -243,16 +278,68 @@ public class PoolFactoryBean implements FactoryBean, InitializingBean, Dis return PoolManager.createFactory(); } - /* (non-Javadoc) */ - @Override - public Class getObjectType() { - return (this.pool != null ? this.pool.getClass() : Pool.class); + /** + * Configures the given {@link PoolFactory} from this {@link PoolFactoryBean}. + * + * @param poolFactory {@link PoolFactory} to configure. + * @return the given {@link PoolFactory}. + * @see org.apache.geode.cache.client.PoolFactory + */ + protected PoolFactory configure(PoolFactory poolFactory) { + + Optional.ofNullable(poolFactory).ifPresent(it -> { + it.setFreeConnectionTimeout(this.freeConnectionTimeout); + it.setIdleTimeout(this.idleTimeout); + it.setLoadConditioningInterval(this.loadConditioningInterval); + it.setMaxConnections(this.maxConnections); + it.setMinConnections(this.minConnections); + it.setMultiuserAuthentication(this.multiUserAuthentication); + it.setPingInterval(this.pingInterval); + it.setPRSingleHopEnabled(this.prSingleHopEnabled); + it.setReadTimeout(this.readTimeout); + it.setRetryAttempts(this.retryAttempts); + it.setServerGroup(this.serverGroup); + it.setSocketBufferSize(this.socketBufferSize); + it.setStatisticInterval(this.statisticInterval); + it.setSubscriptionAckInterval(this.subscriptionAckInterval); + it.setSubscriptionEnabled(this.subscriptionEnabled); + it.setSubscriptionMessageTrackingTimeout(this.subscriptionMessageTrackingTimeout); + it.setSubscriptionRedundancy(this.subscriptionRedundancy); + it.setThreadLocalConnections(this.threadLocalConnections); + + nullSafeCollection(this.locators).forEach(locator -> + it.addLocator(locator.getHost(), locator.getPort())); + + nullSafeCollection(this.servers).forEach(server -> + it.addServer(server.getHost(), server.getPort())); + }); + + return poolFactory; } - /* (non-Javadoc) */ + /** + * Creates a {@link Pool} with the given {@link String name} using the provided {@link PoolFactory}. + * + * @param poolFactory {@link PoolFactory} used to create the {@link Pool}. + * @param poolName {@link String name} of the new {@link Pool}. + * @return a new instance of {@link Pool} with the given {@link String name}. + * @see org.apache.geode.cache.client.PoolFactory#create(String) + * @see org.apache.geode.cache.client.Pool + */ + protected Pool create(PoolFactory poolFactory, String poolName) { + return poolFactory.create(poolName); + } + + /** + * Returns the {@link Class} type of the {@link Pool} produced by this {@link PoolFactoryBean}. + * + * @return the {@link Class} type of the {@link Pool} produced by this {@link PoolFactoryBean}. + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ @Override - public boolean isSingleton() { - return true; + @SuppressWarnings("unchecked") + public Class getObjectType() { + return Optional.ofNullable(this.pool).map(Pool::getClass).orElse((Class) Pool.class); } /* (non-Javadoc) */ @@ -275,29 +362,14 @@ public class PoolFactoryBean implements FactoryBean, InitializingBean, Dis this.servers.add(servers); } - /* (non-Javadoc) */ - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - /* (non-Javadoc) */ - protected BeanFactory getBeanFactory() { - return beanFactory; - } - - /* (non-Javadoc) */ - public void setBeanName(String name) { - this.beanName = name; - } - /* (non-Javadoc) */ public void setName(String name) { this.name = name; } /* (non-Javadoc) */ - String getName() { - return name; + protected String getName() { + return this.name; } /* (non-Javadoc) */ @@ -357,9 +429,8 @@ public class PoolFactoryBean implements FactoryBean, InitializingBean, Dis @Override public String getName() { - String name = PoolFactoryBean.this.name; - name = (StringUtils.hasText(name) ? name : PoolFactoryBean.this.beanName); - return name; + return Optional.ofNullable(PoolFactoryBean.this.getName()).filter(StringUtils::hasText) + .orElseGet(PoolFactoryBean.this::getBeanName); } @Override @@ -524,6 +595,31 @@ public class PoolFactoryBean implements FactoryBean, InitializingBean, Dis this.pingInterval = pingInterval; } + /** + * Null-safe operation to set an array of {@link PoolConfigurer PoolConfigurers} used to apply + * additional configuration to this {@link PoolFactoryBean} when using Annotation-based configuration. + * + * @param poolConfigurers array of {@link PoolConfigurer PoolConfigurers} used to apply + * additional configuration to this {@link PoolFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer + * @see #setPoolConfigurers(List) + */ + public void setPoolConfigurers(PoolConfigurer... poolConfigurers) { + setPoolConfigurers(Arrays.asList(nullSafeArray(poolConfigurers, PoolConfigurer.class))); + } + + /** + * Null-safe operation to set an {@link Iterable} of {@link PoolConfigurer PoolConfigurers} used to apply + * additional configuration to this {@link PoolFactoryBean} when using Annotation-based configuration. + * + * @param poolConfigurers {@link Iterable} of {@link PoolConfigurer PoolConfigurers} used to apply + * additional configuration to this {@link PoolFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer + */ + public void setPoolConfigurers(List poolConfigurers) { + this.poolConfigurers = Optional.ofNullable(poolConfigurers).orElseGet(Collections::emptyList); + } + /* (non-Javadoc) */ public void setPrSingleHopEnabled(boolean prSingleHopEnabled) { this.prSingleHopEnabled = prSingleHopEnabled; @@ -595,11 +691,17 @@ public class PoolFactoryBean implements FactoryBean, InitializingBean, Dis this.threadLocalConnections = threadLocalConnections; } - /* (non-Javadoc; internal framework use only) */ + /* + * (non-Javadoc) + * internal framework use only + */ public final void setLocatorsConfiguration(Object locatorsConfiguration) { } - /* (non-Javadoc; internal framework use only) */ + /* + * (non-Javadoc) + * internal framework use only + */ public final void setServersConfiguration(Object serversConfiguration) { } } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfiguration.java index 04d8a881..2498c20d 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfiguration.java @@ -20,29 +20,26 @@ package org.springframework.data.gemfire.config.annotation; import static org.springframework.data.gemfire.CacheFactoryBean.DynamicRegionSupport; import static org.springframework.data.gemfire.CacheFactoryBean.JndiDataSource; import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeList; -import static org.springframework.data.gemfire.util.SpringUtils.defaultIfNull; import java.lang.annotation.Annotation; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.geode.cache.Cache; import org.apache.geode.cache.TransactionListener; import org.apache.geode.cache.TransactionWriter; +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.server.CacheServer; import org.apache.geode.cache.util.GatewayConflictResolver; import org.apache.geode.pdx.PdxSerializer; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; @@ -50,6 +47,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport; import org.springframework.data.gemfire.config.support.CustomEditorBeanFactoryPostProcessor; import org.springframework.data.gemfire.config.support.DefinedIndexesApplicationListener; import org.springframework.data.gemfire.config.support.DiskStoreDirectoryBeanPostProcessor; @@ -57,33 +55,48 @@ import org.springframework.data.gemfire.config.support.PdxDiskStoreAwareBeanFact import org.springframework.data.gemfire.mapping.GemfireMappingContext; import org.springframework.data.gemfire.mapping.MappingPdxSerializer; import org.springframework.data.gemfire.util.PropertiesBuilder; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * {@link AbstractCacheConfiguration} is an abstract base class for configuring either a Pivotal GemFire/Apache Geode - * client or peer-based cache instance using Spring's Java-based, Annotation - * {@link org.springframework.context.annotation.Configuration} support. + * client or peer-based cache instance using Spring's Java-based, Annotation {@link Configuration} support. * - * This class encapsulates configuration settings common to both GemFire peer - * {@link org.apache.geode.cache.Cache caches} and - * {@link org.apache.geode.cache.client.ClientCache client caches}. + * This class encapsulates configuration settings common to both Pivotal GemFire/Apache Geode + * {@link org.apache.geode.cache.Cache peer caches} + * and {@link org.apache.geode.cache.client.ClientCache client caches}. * * @author John Blum - * @see org.springframework.beans.factory.BeanClassLoaderAware + * @see java.lang.annotation.Annotation + * @see java.util.Properties + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.server.CacheServer + * @see org.apache.geode.pdx.PdxSerializer * @see org.springframework.beans.factory.BeanFactory - * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.config.BeanDefinition + * @see org.springframework.beans.factory.support.BeanDefinitionBuilder + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry * @see org.springframework.context.annotation.Bean * @see org.springframework.context.annotation.Configuration * @see org.springframework.context.annotation.ImportAware + * @see org.springframework.core.convert.ConversionService * @see org.springframework.core.io.Resource * @see org.springframework.core.type.AnnotationMetadata * @see org.springframework.data.gemfire.CacheFactoryBean + * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean + * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport + * @see org.springframework.data.gemfire.config.support.CustomEditorBeanFactoryPostProcessor + * @see org.springframework.data.gemfire.config.support.DefinedIndexesApplicationListener + * @see org.springframework.data.gemfire.config.support.DiskStoreDirectoryBeanPostProcessor + * @see org.springframework.data.gemfire.config.support.PdxDiskStoreAwareBeanFactoryPostProcessor + * @see org.springframework.data.gemfire.mapping.GemfireMappingContext + * @see org.springframework.data.gemfire.mapping.MappingPdxSerializer * @since 1.9.0 */ @Configuration @SuppressWarnings("unused") -public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware, BeanFactoryAware, ImportAware { +public abstract class AbstractCacheConfiguration extends AbstractAnnotationConfigSupport implements ImportAware { private static final AtomicBoolean CUSTOM_EDITORS_REGISTERED = new AtomicBoolean(false); private static final AtomicBoolean DEFINED_INDEXES_APPLICATION_LISTENER_REGISTERED = new AtomicBoolean(false); @@ -104,14 +117,10 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware private boolean copyOnRead = DEFAULT_COPY_ON_READ; private boolean useBeanFactoryLocator = DEFAULT_USE_BEAN_FACTORY_LOCATOR; - private BeanFactory beanFactory; - private Boolean pdxIgnoreUnreadFields; private Boolean pdxPersistent; private Boolean pdxReadSerialized; - private ClassLoader beanClassLoader; - private DynamicRegionSupport dynamicRegionSupport; private Integer mcastPort = 0; @@ -142,59 +151,30 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware private TransactionWriter transactionWriter; /** - * Determines whether the given {@link Object} has value. The {@link Object} is valuable - * if it is not {@literal null}. + * Returns a {@link Properties} object containing Pivotal GemFire/Apache Geode properties used to configure + * the Pivotal GemFire/Apache Geode cache. * - * @param value {@link Object} to evaluate. - * @return a boolean value indicating whether the given {@link Object} has value. - */ - protected static boolean hasValue(Object value) { - return (value != null); - } - - /** - * Determines whether the given {@link Number} has value. The {@link Number} is valuable - * if it is not {@literal null} and is not equal to 0.0d. + * The {@literal name} of the Pivotal GemFire/Apache Geode member/node in the cluster is set to a default, + * pre-defined and descriptive value depending on the type of configuration meta-data applied. * - * @param value {@link Number} to evaluate. - * @return a boolean value indicating whether the given {@link Number} has value. - */ - protected static boolean hasValue(Number value) { - return (value != null && value.doubleValue() != 0.0d); - } - - /** - * Determines whether the given {@link String} has value. The {@link String} is valuable - * if it is not {@literal null} or empty. + * {@literal mcast-port} is set to {@literal 0} and {@literal locators} is set to an {@link String empty String}, + * which is necessary for {@link ClientCache cache client}-based applications. These values can be changed + * and set accoridingly for {@link Cache peer cache} and {@link CacheServer cache server} applications. * - * @param value {@link String} to evaluate. - * @return a boolean value indicating whether the given {@link String} is valuable. - */ - protected static boolean hasValue(String value) { - return StringUtils.hasText(value); - } - - /** - * Returns a {@link Properties} object containing GemFire System properties used to configure the GemFire cache. + * Finally, the {@literal log-level} property defaults to {@literal config}. * - * The name of the GemFire member/node in the cluster is set to a default, pre-defined, descriptive value - * depending on the type of configuration meta-data applied. - * - * Both 'mcast-port' and 'locators' are to set 0 and empty String respectively, which is necessary - * for {@link org.apache.geode.cache.client.ClientCache cache client}-based applications. These values - * can be changed for peer cache and cache server applications. - * - * Finally, GemFire's {@literal log-level} System property defaults to {@literal config}. - * - * @return a {@link Properties} object containing GemFire System properties used to configure the GemFire cache. + * @return a {@link Properties} object containing Pivotal GemFire/Apache Geode properties used to configure + * the Pivotal GemFire/Apache Geode cache instance. * @see GemFire Properties * @see java.util.Properties - * @see #name() - * @see #logLevel() * @see #locators() + * @see #logLevel() + * @see #mcastPort() + * @see #name() */ @Bean protected Properties gemfireProperties() { + PropertiesBuilder gemfireProperties = PropertiesBuilder.create(); gemfireProperties.setProperty("name", name()); @@ -202,100 +182,87 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware gemfireProperties.setProperty("log-level", logLevel()); gemfireProperties.setProperty("locators", locators()); gemfireProperties.setProperty("start-locator", startLocator()); - gemfireProperties.add(customGemFireProperties); + gemfireProperties.add(this.customGemFireProperties); return gemfireProperties.build(); } - /** - * {@inheritDoc} - */ - @Override - public void setBeanClassLoader(ClassLoader beanClassLoader) { - this.beanClassLoader = beanClassLoader; - } - - /** - * Returns a reference to the {@link ClassLoader} use by the Spring {@link BeanFactory} to load classes - * for bean definitions. - * - * @return the {@link ClassLoader} used by the Spring {@link BeanFactory} to load classes for bean definitions. - * @see #setBeanClassLoader(ClassLoader) - */ - protected ClassLoader beanClassLoader() { - return beanClassLoader; - } - - /** - * {@inheritDoc} - */ - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - /** - * Returns a reference to the Spring {@link BeanFactory} in the current application context. - * - * @return a reference to the Spring {@link BeanFactory}. - * @throws IllegalStateException if the Spring {@link BeanFactory} was not properly initialized. - * @see org.springframework.beans.factory.BeanFactory - */ - protected BeanFactory beanFactory() { - Assert.state(this.beanFactory != null, "BeanFactory was not properly initialized"); - return this.beanFactory; - } - /** * {@inheritDoc} */ @Override public void setImportMetadata(AnnotationMetadata importMetadata) { + configureInfrastructure(importMetadata); configureCache(importMetadata); configurePdx(importMetadata); - configureOther(importMetadata); + configureTheRest(importMetadata); } /** - * Configures Spring container infrastructure components used by Spring Data GemFire - * to enable GemFire to function properly inside a Spring context. + * Configures Spring container infrastructure components and beans used by Spring Data GemFire + * to enable Pivotal GemFire or Apache Geode to function properly inside a Spring context. * * @param importMetadata {@link AnnotationMetadata} containing annotation meta-data - * for the Spring GemFire cache application class. + * for the Spring Data GemFire cache application class. * @see org.springframework.core.type.AnnotationMetadata */ protected void configureInfrastructure(AnnotationMetadata importMetadata) { + registerCustomEditorBeanFactoryPostProcessor(importMetadata); registerDefinedIndexesApplicationListener(importMetadata); registerDiskStoreDirectoryBeanPostProcessor(importMetadata); } + /* (non-Javadoc) */ + private void registerCustomEditorBeanFactoryPostProcessor(AnnotationMetadata importMetadata) { + + if (CUSTOM_EDITORS_REGISTERED.compareAndSet(false, true)) { + register(BeanDefinitionBuilder.rootBeanDefinition(CustomEditorBeanFactoryPostProcessor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); + } + } + + /* (non-Javadoc) */ + private void registerDefinedIndexesApplicationListener(AnnotationMetadata importMetadata) { + + if (DEFINED_INDEXES_APPLICATION_LISTENER_REGISTERED.compareAndSet(false, true)) { + register(BeanDefinitionBuilder.rootBeanDefinition(DefinedIndexesApplicationListener.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); + } + } + + /* (non-Javadoc) */ + private void registerDiskStoreDirectoryBeanPostProcessor(AnnotationMetadata importMetadata) { + + if (DISK_STORE_DIRECTORY_BEAN_POST_PROCESSOR_REGISTERED.compareAndSet(false, true)) { + register(BeanDefinitionBuilder.rootBeanDefinition(DiskStoreDirectoryBeanPostProcessor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); + } + } + /** - * Configures the GemFire cache settings. + * Configures Pivotal GemFire/Apache Geode cache specific settings. * - * @param importMetadata {@link AnnotationMetadata} containing the cache meta-data used to configure - * the GemFire cache. + * @param importMetadata {@link AnnotationMetadata} containing the cache meta-data used to configure the cache. * @see org.springframework.core.type.AnnotationMetadata */ protected void configureCache(AnnotationMetadata importMetadata) { + if (isClientPeerOrServerCacheApplication(importMetadata)) { + Map cacheMetadataAttributes = importMetadata.getAnnotationAttributes(getAnnotationTypeName()); setCopyOnRead(Boolean.TRUE.equals(cacheMetadataAttributes.get("copyOnRead"))); - Float criticalHeapPercentage = (Float) cacheMetadataAttributes.get("criticalHeapPercentage"); + Optional.ofNullable((Float) cacheMetadataAttributes.get("criticalHeapPercentage")) + .filter(AbstractAnnotationConfigSupport::hasValue) + .ifPresent(this::setCriticalHeapPercentage); - if (hasValue(criticalHeapPercentage)) { - setCriticalHeapPercentage(criticalHeapPercentage); - } - - Float evictionHeapPercentage = (Float) cacheMetadataAttributes.get("evictionHeapPercentage"); - - if (hasValue(evictionHeapPercentage)) { - setEvictionHeapPercentage(evictionHeapPercentage); - } + Optional.ofNullable((Float) cacheMetadataAttributes.get("evictionHeapPercentage")) + .filter(AbstractAnnotationConfigSupport::hasValue) + .ifPresent(this::setEvictionHeapPercentage); setLogLevel((String) cacheMetadataAttributes.get("logLevel")); setName((String) cacheMetadataAttributes.get("name")); @@ -304,17 +271,19 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware } /** - * Configures GemFire's PDX Serialization components. + * Configures Pivotal GemFire/Apache Geode cache PDX Serialization. * - * @param importMetadata {@link AnnotationMetadata} containing PDX meta-data used to configure - * the GemFire cache with PDX de/serialization capabilities. + * @param importMetadata {@link AnnotationMetadata} containing PDX meta-data used to configure the cache + * with PDX de/serialization capabilities. * @see org.springframework.core.type.AnnotationMetadata * @see GemFire PDX Serialization */ protected void configurePdx(AnnotationMetadata importMetadata) { + String enablePdxTypeName = EnablePdx.class.getName(); if (importMetadata.hasAnnotation(enablePdxTypeName)) { + Map enablePdxAttributes = importMetadata.getAnnotationAttributes(enablePdxTypeName); setPdxDiskStoreName((String) enablePdxAttributes.get("diskStoreName")); @@ -328,97 +297,158 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware } /** - * Callback method to configure other, specific GemFire cache configuration settings. + * Resolves the {@link PdxSerializer} used to configure the cache for PDX De/Serialization. * - * @param importMetadata {@link AnnotationMetadata} containing meta-data used to configure - * the GemFire cache. - * @see org.springframework.core.type.AnnotationMetadata - */ - protected void configureOther(AnnotationMetadata importMetadata) { - } - - /* (non-Javadoc) */ - protected PdxSerializer resolvePdxSerializer(String pdxSerializerBeanName) { - BeanFactory beanFactory = beanFactory(); - PdxSerializer pdxSerializer = pdxSerializer(); - - return (beanFactory.containsBean(pdxSerializerBeanName) - ? beanFactory.getBean(pdxSerializerBeanName, PdxSerializer.class) - : (pdxSerializer != null ? pdxSerializer : newPdxSerializer())); - } - - /* (non-Javadoc) */ - @SuppressWarnings("unchecked") - protected T newPdxSerializer() { - BeanFactory beanFactory = beanFactory(); - - ConversionService conversionService = (beanFactory instanceof ConfigurableBeanFactory - ? ((ConfigurableBeanFactory) beanFactory).getConversionService() : null); - - return (T) MappingPdxSerializer.create(this.mappingContext, conversionService); - } - - /* (non-Javadoc) */ - protected void registerCustomEditorBeanFactoryPostProcessor(AnnotationMetadata importMetadata) { - if (CUSTOM_EDITORS_REGISTERED.compareAndSet(false, true)) { - register(BeanDefinitionBuilder.rootBeanDefinition(CustomEditorBeanFactoryPostProcessor.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); - } - } - - /* (non-Javadoc) */ - protected void registerDefinedIndexesApplicationListener(AnnotationMetadata importMetadata) { - if (DEFINED_INDEXES_APPLICATION_LISTENER_REGISTERED.compareAndSet(false, true)) { - register(BeanDefinitionBuilder.rootBeanDefinition(DefinedIndexesApplicationListener.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); - } - } - - /* (non-Javadoc) */ - protected void registerDiskStoreDirectoryBeanPostProcessor(AnnotationMetadata importMetadata) { - if (DISK_STORE_DIRECTORY_BEAN_POST_PROCESSOR_REGISTERED.compareAndSet(false, true)) { - register(BeanDefinitionBuilder.rootBeanDefinition(DiskStoreDirectoryBeanPostProcessor.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); - } - } - - /* (non-Javadoc) */ - protected void registerPdxDiskStoreAwareBeanFactoryPostProcessor(AnnotationMetadata importMetadata) { - if (StringUtils.hasText(pdxDiskStoreName())) { - if (PDX_DISK_STORE_AWARE_BEAN_FACTORY_POST_PROCESSOR_REGISTERED.compareAndSet(false, true)) { - register(BeanDefinitionBuilder.rootBeanDefinition(PdxDiskStoreAwareBeanFactoryPostProcessor.class) - .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) - .addConstructorArgValue(pdxDiskStoreName()) - .getBeanDefinition()); - } - } - } - - /** - * Registers the given {@link BeanDefinition} with the {@link BeanDefinitionRegistry} using a generated bean name. - * - * @param beanDefinition {@link AbstractBeanDefinition} to register. - * @return the given {@link BeanDefinition}. - * @see org.springframework.beans.factory.support.AbstractBeanDefinition - * @see org.springframework.beans.factory.support.BeanDefinitionRegistry - * @see org.springframework.beans.factory.support.BeanDefinitionReaderUtils - * #registerWithGeneratedName(AbstractBeanDefinition, BeanDefinitionRegistry) + * @param pdxSerializerBeanName {@link String} containing the name of a Spring bean + * implementing the {@link PdxSerializer} interface. + * @return the resolved {@link PdxSerializer} from configuration. + * @see org.apache.geode.pdx.PdxSerializer + * @see #newPdxSerializer(BeanFactory) * @see #beanFactory() */ - protected AbstractBeanDefinition register(AbstractBeanDefinition beanDefinition) { - if (beanFactory() instanceof BeanDefinitionRegistry) { - BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, - ((BeanDefinitionRegistry) beanFactory())); - } + protected PdxSerializer resolvePdxSerializer(String pdxSerializerBeanName) { - return beanDefinition; + BeanFactory beanFactory = beanFactory(); + + return Optional.ofNullable(pdxSerializerBeanName) + .filter(beanFactory::containsBean) + .map(beanName -> beanFactory.getBean(beanName, PdxSerializer.class)) + .orElseGet(() -> Optional.ofNullable(pdxSerializer()).orElseGet(() -> newPdxSerializer(beanFactory))); } /** - * Returns the GemFire cache application {@link java.lang.annotation.Annotation} type pertaining to - * this configuration. + * Constructs a new instance of {@link PdxSerializer}. * - * @return the GemFire cache application {@link java.lang.annotation.Annotation} type used by this application. + * @param {@link Class} type of the {@link PdxSerializer}. + * @return a new instance of {@link PdxSerializer}. + * @see org.apache.geode.pdx.PdxSerializer + * @see #newPdxSerializer(BeanFactory) + */ + @SuppressWarnings("unchecked") + protected T newPdxSerializer() { + return newPdxSerializer(beanFactory()); + } + + /** + * Constructs a new instance of {@link MappingPdxSerializer}. + * + * @param {@link Class} type of the {@link PdxSerializer}; this method returns a {@link MappingPdxSerializer}. + * @param beanFactory {@link BeanFactory} used to get an instance of {@link ConversionService} + * used by the {@link MappingPdxSerializer}. + * @return a new instance of {@link MappingPdxSerializer}. + * @see org.springframework.data.gemfire.mapping.MappingPdxSerializer + * @see org.springframework.beans.factory.BeanFactory + * @see org.apache.geode.pdx.PdxSerializer + */ + @SuppressWarnings("unchecked") + protected T newPdxSerializer(BeanFactory beanFactory) { + + Optional conversionService = Optional.ofNullable(beanFactory) + .filter(it -> it instanceof ConfigurableBeanFactory) + .map(it -> ((ConfigurableBeanFactory) it).getConversionService()); + + return (T) MappingPdxSerializer.create(this.mappingContext, conversionService.orElse(null)); + } + + /* (non-Javadoc) */ + private void registerPdxDiskStoreAwareBeanFactoryPostProcessor(AnnotationMetadata importMetadata) { + + Optional.ofNullable(pdxDiskStoreName()) + .filter(StringUtils::hasText) + .ifPresent(pdxDiskStoreName -> { + if (PDX_DISK_STORE_AWARE_BEAN_FACTORY_POST_PROCESSOR_REGISTERED.compareAndSet(false, true)) { + register(BeanDefinitionBuilder.rootBeanDefinition(PdxDiskStoreAwareBeanFactoryPostProcessor.class) + .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) + .addConstructorArgValue(pdxDiskStoreName) + .getBeanDefinition()); + } + }); + } + + /** + * Callback method allowing developers to configure other cache or application specific configuration settings. + * + * @param importMetadata {@link AnnotationMetadata} containing meta-data used to configure the cache or application. + * @see org.springframework.core.type.AnnotationMetadata + */ + protected void configureTheRest(AnnotationMetadata importMetadata) { + } + + /** + * Constructs a new, initialized instance of {@link CacheFactoryBean} based on the Spring application's + * cache type preference (i.e. client or peer), which is expressed via the appropriate annotation. + * + * Use the {@link ClientCacheApplication} Annotation to construct a {@link ClientCache cache client} application. + * + * Use the {@link PeerCacheApplication} Annotation to construct a {@link Cache peer cache} application. + * + * @param {@link Class} specific sub-type of the {@link CacheFactoryBean}. + * @return a new instance of the appropriate {@link CacheFactoryBean} given the Spring application's + * cache type preference (i.e client or peer), (e.g. {@link ClientCacheApplication} + * or {@link PeerCacheApplication}). + * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean + * @see org.springframework.data.gemfire.CacheFactoryBean + * @see #configureCacheFactoryBean(CacheFactoryBean) + * @see #newCacheFactoryBean() + */ + protected T constructCacheFactoryBean() { + return configureCacheFactoryBean(this.newCacheFactoryBean()); + } + + /** + * Constructs a new, uninitialized instance of {@link CacheFactoryBean} based on the Spring application's + * cache type preference (i.e. client or peer), which is expressed via the appropriate annotation. + * + * Use the {@link ClientCacheApplication} Annotation to construct a {@link ClientCache cache client} application. + * + * Use the {@link PeerCacheApplication} Annotation to construct a {@link Cache peer cache} application. + * + * @param {@link Class} specific sub-type of the {@link CacheFactoryBean}. + * @return a new instance of the appropriate {@link CacheFactoryBean} given the Spring application's + * cache type preference (i.e client or peer), (e.g. {@link ClientCacheApplication} + * or {@link PeerCacheApplication}). + * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean + * @see org.springframework.data.gemfire.CacheFactoryBean + */ + protected abstract T newCacheFactoryBean(); + + /** + * Configures the {@link CacheFactoryBean} with common cache configuration settings. + * + * @param {@link Class} specific sub-type of the {@link CacheFactoryBean}. + * @param gemfireCache {@link CacheFactoryBean} to configure. + * @return the given {@link CacheFactoryBean} with common cache configuration settings applied. + * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean + * @see org.springframework.data.gemfire.CacheFactoryBean + */ + protected T configureCacheFactoryBean(T gemfireCache) { + + gemfireCache.setBeanClassLoader(beanClassLoader()); + gemfireCache.setBeanFactory(beanFactory()); + gemfireCache.setCacheXml(cacheXml()); + gemfireCache.setClose(close()); + gemfireCache.setCopyOnRead(copyOnRead()); + gemfireCache.setCriticalHeapPercentage(criticalHeapPercentage()); + gemfireCache.setDynamicRegionSupport(dynamicRegionSupport()); + gemfireCache.setEvictionHeapPercentage(evictionHeapPercentage()); + gemfireCache.setGatewayConflictResolver(gatewayConflictResolver()); + gemfireCache.setJndiDataSources(jndiDataSources()); + gemfireCache.setProperties(gemfireProperties()); + gemfireCache.setPdxDiskStoreName(pdxDiskStoreName()); + gemfireCache.setPdxIgnoreUnreadFields(pdxIgnoreUnreadFields()); + gemfireCache.setPdxPersistent(pdxPersistent()); + gemfireCache.setPdxReadSerialized(pdxReadSerialized()); + gemfireCache.setPdxSerializer(pdxSerializer()); + gemfireCache.setTransactionListeners(transactionListeners()); + gemfireCache.setTransactionWriter(transactionWriter()); + + return gemfireCache; + } + + /** + * Returns the cache application {@link java.lang.annotation.Annotation} type pertaining to this configuration. + * + * @return the cache application {@link java.lang.annotation.Annotation} type used by this application. * @see org.springframework.data.gemfire.config.annotation.CacheServerApplication * @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication * @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication @@ -426,11 +456,11 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware protected abstract Class getAnnotationType(); /** - * Returns the fully-qualified class name of the GemFire cache application {@link java.lang.annotation.Annotation} - * type. + * Returns the fully-qualified {@link Class#getName() class name} of the cache application + * {@link java.lang.annotation.Annotation} type. * - * @return the fully-qualified class name of the GemFire cache application {@link java.lang.annotation.Annotation} - * type. + * @return the fully-qualified {@link Class#getName() class name} of the cache application + * {@link java.lang.annotation.Annotation} type. * @see java.lang.Class#getName() * @see #getAnnotationType() */ @@ -439,9 +469,11 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware } /** - * Returns the simple class name of the GemFire cache application {@link java.lang.annotation.Annotation} type. + * Returns the simple {@link Class#getName() class name} of the cache application + * {@link java.lang.annotation.Annotation} type. * - * @return the simple class name of the GemFire cache application {@link java.lang.annotation.Annotation} type. + * @return the simple {@link Class#getName() class name} of the cache application + * {@link java.lang.annotation.Annotation} type. * @see java.lang.Class#getSimpleName() * @see #getAnnotationType() */ @@ -449,6 +481,8 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware return getAnnotationType().getSimpleName(); } + // REVIEW JAVADOC FROM HERE + /** * Determines whether this is a GemFire {@link org.apache.geode.cache.server.CacheServer} application, * which is indicated by the presence of the {@link CacheServerApplication} annotation on a Spring application @@ -508,7 +542,7 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware * @see #getAnnotationType() */ protected boolean isTypedCacheApplication(Class annotationType, - AnnotationMetadata importMetadata) { + AnnotationMetadata importMetadata) { return (annotationType.equals(getAnnotationType()) && importMetadata.hasAnnotation(getAnnotationTypeName())); } @@ -549,68 +583,6 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware || isPeerCacheApplication(importMetadata)); } - /** - * Constructs a new, initialized instance of the {@link CacheFactoryBean} based on the Spring application's - * GemFire cache type (i.e. client or peer) preference specified via annotation. - * - * @param Class type of the {@link CacheFactoryBean}. - * @return a new instance of an appropriate {@link CacheFactoryBean} given the Spring application's - * GemFire cache type preference (i.e client or peer) specified with the corresponding annotation - * (e.g. {@link ClientCacheApplication} or {@link PeerCacheApplication}); - * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean - * @see org.springframework.data.gemfire.CacheFactoryBean - * @see #setCommonCacheConfiguration(CacheFactoryBean) - * @see #newCacheFactoryBean() - */ - protected T constructCacheFactoryBean() { - return setCommonCacheConfiguration(this.newCacheFactoryBean()); - } - - /** - * Constructs a new, uninitialized instance of the {@link CacheFactoryBean} based on the Spring application's - * GemFire cache type (i.e. client or peer) preference specified via annotation. - * - * @param Class type of the {@link CacheFactoryBean}. - * @return a new instance of an appropriate {@link CacheFactoryBean} given the Spring application's - * GemFire cache type preference (i.e client or peer) specified with the corresponding annotation - * (e.g. {@link ClientCacheApplication} or {@link PeerCacheApplication}). - * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean - * @see org.springframework.data.gemfire.CacheFactoryBean - */ - protected abstract T newCacheFactoryBean(); - - /** - * Configures common GemFire cache configuration settings. - * - * @param Class type of the {@link CacheFactoryBean}. - * @param gemfireCache {@link CacheFactoryBean} instance to configure. - * @return the given {@link CacheFactoryBean} after common configuration settings have been applied. - * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean - * @see org.springframework.data.gemfire.CacheFactoryBean - */ - protected T setCommonCacheConfiguration(T gemfireCache) { - gemfireCache.setBeanClassLoader(beanClassLoader()); - gemfireCache.setBeanFactory(beanFactory()); - gemfireCache.setCacheXml(cacheXml()); - gemfireCache.setClose(close()); - gemfireCache.setCopyOnRead(copyOnRead()); - gemfireCache.setCriticalHeapPercentage(criticalHeapPercentage()); - gemfireCache.setDynamicRegionSupport(dynamicRegionSupport()); - gemfireCache.setEvictionHeapPercentage(evictionHeapPercentage()); - gemfireCache.setGatewayConflictResolver(gatewayConflictResolver()); - gemfireCache.setJndiDataSources(jndiDataSources()); - gemfireCache.setProperties(gemfireProperties()); - gemfireCache.setPdxDiskStoreName(pdxDiskStoreName()); - gemfireCache.setPdxIgnoreUnreadFields(pdxIgnoreUnreadFields()); - gemfireCache.setPdxPersistent(pdxPersistent()); - gemfireCache.setPdxReadSerialized(pdxReadSerialized()); - gemfireCache.setPdxSerializer(pdxSerializer()); - gemfireCache.setTransactionListeners(transactionListeners()); - gemfireCache.setTransactionWriter(transactionWriter()); - - return gemfireCache; - } - /* (non-Javadoc) */ void setCacheXml(Resource cacheXml) { this.cacheXml = cacheXml; @@ -699,7 +671,7 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware } protected String logLevel() { - return defaultIfNull(this.logLevel, DEFAULT_LOG_LEVEL); + return Optional.ofNullable(this.logLevel).orElse(DEFAULT_LOG_LEVEL); } /* (non-Javadoc) */ @@ -717,7 +689,7 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware } protected Integer mcastPort() { - return (mcastPort != null ? mcastPort : DEFAULT_MCAST_PORT); + return Optional.ofNullable(mcastPort).orElse(DEFAULT_MCAST_PORT); } /* (non-Javadoc) */ @@ -726,7 +698,7 @@ public abstract class AbstractCacheConfiguration implements BeanClassLoaderAware } protected String name() { - return (StringUtils.hasText(name) ? name : toString()); + return Optional.ofNullable(this.name).filter(StringUtils::hasText).orElseGet(this::toString); } /* (non-Javadoc) */ diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/AddCacheServerConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/AddCacheServerConfiguration.java index 1b51fa0f..a1738804 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/AddCacheServerConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/AddCacheServerConfiguration.java @@ -17,8 +17,22 @@ package org.springframework.data.gemfire.config.annotation; -import java.util.Map; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -26,27 +40,49 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.config.xml.GemfireConstants; import org.springframework.data.gemfire.server.CacheServerFactoryBean; +import org.springframework.util.StringUtils; /** * The {@link AddCacheServerConfiguration} class is a Spring {@link ImportBeanDefinitionRegistrar} that registers * a {@link CacheServerFactoryBean} definition for the {@link org.apache.geode.cache.server.CacheServer} - * configuration meta-data defined in {@link EnableCacheServer}. + * configuration meta-data defined in {@link EnableCacheServer} annotation. * * @author John Blum + * @see org.apache.geode.cache.server.CacheServer + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.config.BeanDefinition + * @see org.springframework.beans.factory.config.BeanDefinitionHolder + * @see org.springframework.beans.factory.support.BeanDefinitionBuilder + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar + * @see org.springframework.core.type.AnnotationMetadata + * @see org.springframework.data.gemfire.config.annotation.AddCacheServersConfiguration + * @see org.springframework.data.gemfire.config.annotation.CacheServerApplication + * @see org.springframework.data.gemfire.config.annotation.CacheServerConfiguration + * @see org.springframework.data.gemfire.config.annotation.CacheServerConfigurer + * @see org.springframework.data.gemfire.config.annotation.EnableCacheServers * @see org.springframework.data.gemfire.config.annotation.EnableCacheServer + * @see org.springframework.data.gemfire.server.CacheServerFactoryBean * @since 1.9.0 */ -public class AddCacheServerConfiguration implements ImportBeanDefinitionRegistrar { +public class AddCacheServerConfiguration implements BeanFactoryAware, ImportBeanDefinitionRegistrar { + + private BeanFactory beanFactory; + + @Autowired(required = false) + private List cacheServerConfigurers = Collections.emptyList(); /** * {@inheritDoc} */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (importingClassMetadata.hasAnnotation(EnableCacheServer.class.getName())) { - Map enableCacheServerAttributes = importingClassMetadata.getAnnotationAttributes( - EnableCacheServer.class.getName()); + + Map enableCacheServerAttributes = + importingClassMetadata.getAnnotationAttributes(EnableCacheServer.class.getName()); registerCacheServerFactoryBeanDefinition(enableCacheServerAttributes, registry); } @@ -69,6 +105,7 @@ public class AddCacheServerConfiguration implements ImportBeanDefinitionRegistra BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CacheServerFactoryBean.class); builder.addPropertyReference("cache", GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME); + builder.addPropertyValue("cacheServerConfigurers", resolveCacheServerConfigurers()); builder.addPropertyValue("autoStartup", enableCacheServerAttributes.get("autoStartup")); builder.addPropertyValue("bindAddress", enableCacheServerAttributes.get("bindAddress")); builder.addPropertyValue("hostNameForClients", enableCacheServerAttributes.get("hostnameForClients")); @@ -84,6 +121,49 @@ public class AddCacheServerConfiguration implements ImportBeanDefinitionRegistra builder.addPropertyValue("subscriptionDiskStore", enableCacheServerAttributes.get("subscriptionDiskStoreName")); builder.addPropertyValue("subscriptionEvictionPolicy", enableCacheServerAttributes.get("subscriptionEvictionPolicy")); - BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), registry); + registerCacheServerFactoryBeanDefinition(builder.getBeanDefinition(), + (String) enableCacheServerAttributes.get("name"), registry); + } + + /* (non-Javadoc) */ + private List resolveCacheServerConfigurers() { + + return Optional.ofNullable(this.cacheServerConfigurers) + .filter(cacheServerConfigurers -> !cacheServerConfigurers.isEmpty()) + .orElseGet(() -> + Optional.of(this.beanFactory) + .filter(beanFactory -> beanFactory instanceof ListableBeanFactory) + .map(beanFactory -> { + Map beansOfType = ((ListableBeanFactory) beanFactory) + .getBeansOfType(CacheServerConfigurer.class, true, true); + + return nullSafeMap(beansOfType).values().stream().collect(Collectors.toList()); + }) + .orElseGet(Collections::emptyList) + ); + + } + /* (non-Javadoc) */ + protected void registerCacheServerFactoryBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName, + BeanDefinitionRegistry registry) { + + if (StringUtils.hasText(beanName)) { + BeanDefinitionReaderUtils.registerBeanDefinition( + newBeanDefinitionHolder(beanDefinition, beanName), registry); + } + else { + BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry); + } + } + + /* (non-Javadoc) */ + protected BeanDefinitionHolder newBeanDefinitionHolder(BeanDefinition beanDefinition, String beanName) { + return new BeanDefinitionHolder(beanDefinition, beanName); + } + + /* (non-Javadoc) */ + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; } } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/AddPoolConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/AddPoolConfiguration.java index 3de83bea..a672d2cf 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/AddPoolConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/AddPoolConfiguration.java @@ -17,8 +17,19 @@ package org.springframework.data.gemfire.config.annotation; -import java.util.Map; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; @@ -32,25 +43,42 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * The {@link AddCacheServerConfiguration} class is a Spring {@link ImportBeanDefinitionRegistrar} that registers + * The {@link AddPoolConfiguration} class is a Spring {@link ImportBeanDefinitionRegistrar} that registers * a {@link PoolFactoryBean} definition for the {@link org.apache.geode.cache.client.Pool} - * configuration meta-data defined in {@link EnablePool}. + * configuration meta-data defined in {@link EnablePool} annotations. * * @author John Blum + * @see org.apache.geode.cache.client.Pool + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.support.BeanDefinitionBuilder + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar + * @see org.springframework.core.type.AnnotationMetadata + * @see org.springframework.data.gemfire.client.PoolFactoryBean + * @see org.springframework.data.gemfire.config.annotation.AddPoolsConfiguration + * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer + * @see org.springframework.data.gemfire.config.annotation.EnablePools * @see org.springframework.data.gemfire.config.annotation.EnablePool * @since 1.9.0 */ -public class AddPoolConfiguration implements ImportBeanDefinitionRegistrar { +public class AddPoolConfiguration implements BeanFactoryAware, ImportBeanDefinitionRegistrar { + + private BeanFactory beanFactory; + + @Autowired(required = false) + private List poolConfigurers = Collections.emptyList(); /** * {@inheritDoc} */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (importingClassMetadata.hasAnnotation(EnablePool.class.getName())) { - Map enablePoolAttributes = importingClassMetadata.getAnnotationAttributes( - EnablePool.class.getName()); + + Map enablePoolAttributes = + importingClassMetadata.getAnnotationAttributes(EnablePool.class.getName()); registerPoolFactoryBeanDefinition(enablePoolAttributes, registry); } @@ -61,7 +89,7 @@ public class AddPoolConfiguration implements ImportBeanDefinitionRegistrar { * the {@link EnablePool} annotation meta-data. * * @param enablePoolAttributes {@link EnablePool} annotation attributes. - * @param registry Spring {@link BeanDefinitionRegistry used to register the {@link PoolFactoryBean} definition. + * @param registry Spring {@link BeanDefinitionRegistry} used to register the {@link PoolFactoryBean} definition. * @see org.springframework.beans.factory.support.BeanDefinitionRegistry * @see org.springframework.data.gemfire.client.PoolFactoryBean * @see org.springframework.data.gemfire.config.annotation.EnablePool @@ -81,6 +109,7 @@ public class AddPoolConfiguration implements ImportBeanDefinitionRegistrar { poolFactoryBean.addPropertyValue("minConnections", enablePoolAttributes.get("minConnections")); poolFactoryBean.addPropertyValue("multiUserAuthentication", enablePoolAttributes.get("multiUserAuthentication")); poolFactoryBean.addPropertyValue("pingInterval", enablePoolAttributes.get("pingInterval")); + poolFactoryBean.addPropertyValue("poolConfigurers", resolvePoolConfigurers()); poolFactoryBean.addPropertyValue("prSingleHopEnabled", enablePoolAttributes.get("prSingleHopEnabled")); poolFactoryBean.addPropertyValue("readTimeout", enablePoolAttributes.get("readTimeout")); poolFactoryBean.addPropertyValue("retryAttempts", enablePoolAttributes.get("retryAttempts")); @@ -98,9 +127,27 @@ public class AddPoolConfiguration implements ImportBeanDefinitionRegistrar { registry.registerBeanDefinition(poolName, poolFactoryBean.getBeanDefinition()); } + /* (non-Javadoc) */ + private List resolvePoolConfigurers() { + + return Optional.ofNullable(this.poolConfigurers) + .filter(poolConfigurers -> !poolConfigurers.isEmpty()) + .orElseGet(() -> + Optional.of(this.beanFactory) + .filter(beanFactory -> beanFactory instanceof ListableBeanFactory) + .map(beanFactory -> { + Map beansOfType = ((ListableBeanFactory) beanFactory) + .getBeansOfType(PoolConfigurer.class, true, true); + + return nullSafeMap(beansOfType).values().stream().collect(Collectors.toList()); + }) + .orElseGet(Collections::emptyList) + ); + } + protected String getAndValidatePoolName(Map enablePoolAttributes) { String poolName = (String) enablePoolAttributes.get("name"); - Assert.hasText(poolName, "Pool name must be specified"); + Assert.hasText(poolName, "Pool name is required"); return poolName; } @@ -166,4 +213,9 @@ public class AddPoolConfiguration implements ImportBeanDefinitionRegistrar { protected ConnectionEndpoint newConnectionEndpoint(String host, Integer port) { return new ConnectionEndpoint(host, port); } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/AddPoolsConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/AddPoolsConfiguration.java index c4523ded..ef67d0a1 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/AddPoolsConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/AddPoolsConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.core.type.AnnotationMetadata; * the {@link EnablePools} annotation on a GemFire client cache application class. * @author John Blum + * @see org.apache.geode.cache.client.Pool * @see org.springframework.data.gemfire.config.annotation.AddPoolConfiguration * @see org.springframework.data.gemfire.config.annotation.EnablePool * @see org.springframework.data.gemfire.config.annotation.EnablePools diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerApplication.java b/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerApplication.java index f8ebfa37..19eef694 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerApplication.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerApplication.java @@ -43,6 +43,8 @@ import org.springframework.data.gemfire.server.SubscriptionEvictionPolicy; * @see org.springframework.context.annotation.Configuration * @see org.springframework.context.annotation.Import * @see org.springframework.data.gemfire.config.annotation.CacheServerConfiguration + * @see org.springframework.data.gemfire.config.annotation.EnableCacheServers + * @see org.springframework.data.gemfire.config.annotation.EnableCacheServer * @see org.apache.geode.cache.control.ResourceManager * @see org.apache.geode.cache.server.CacheServer * @see org.apache.geode.cache.server.ClientSubscriptionConfig diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerConfiguration.java index 4911928c..c867231b 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerConfiguration.java @@ -17,32 +17,47 @@ package org.springframework.data.gemfire.config.annotation; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeSet; + +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import org.apache.geode.cache.Cache; import org.apache.geode.cache.InterestRegistrationListener; +import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.server.CacheServer; import org.apache.geode.cache.server.ClientSubscriptionConfig; import org.apache.geode.cache.server.ServerLoadProbe; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.server.CacheServerFactoryBean; import org.springframework.data.gemfire.server.SubscriptionEvictionPolicy; -import org.springframework.data.gemfire.util.CollectionUtils; -import org.springframework.data.gemfire.util.SpringUtils; +import org.springframework.util.StringUtils; /** - * Spring {@link Configuration} class used to configure, construct and initialize and GemFire {@link CacheServer} - * instance in a Spring application context. + * Spring {@link Configuration} class used to construct, configure and initialize a {@link CacheServer} instance + * in a Spring application context. * * @author John Blum - * @see org.springframework.context.annotation.Bean - * @see org.springframework.context.annotation.Configuration - * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfiguration * @see org.apache.geode.cache.Cache * @see org.apache.geode.cache.server.CacheServer + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.data.gemfire.config.annotation.AddCacheServerConfiguration + * @see org.springframework.data.gemfire.config.annotation.AddCacheServersConfiguration + * @see org.springframework.data.gemfire.config.annotation.CacheServerConfigurer + * @see org.springframework.data.gemfire.config.annotation.EnableCacheServer + * @see org.springframework.data.gemfire.config.annotation.EnableCacheServers + * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfiguration + * @see org.springframework.data.gemfire.server.CacheServerFactoryBean * @since 1.9.0 */ @Configuration @@ -64,6 +79,9 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { private Integer socketBufferSize; private Integer subscriptionCapacity; + @Autowired(required = false) + private List cacheServerConfigurers = Collections.emptyList(); + private Long loadPollInterval; private ServerLoadProbe serverLoadProbe; @@ -76,11 +94,23 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { private SubscriptionEvictionPolicy subscriptionEvictionPolicy; + /** + * Bean declaration for a single, {@link CacheServer} to serve {@link ClientCache cache client} applications. + * + * @param gemfireCache peer {@link Cache} instance in which to add the {@link CacheServer}. + * @return a {@link CacheServerFactoryBean} used to construct, configure and initialize + * the {@link CacheServer} instance. + * @see org.springframework.data.gemfire.server.CacheServerFactoryBean + * @see org.apache.geode.cache.server.CacheServer + * @see org.apache.geode.cache.Cache + */ @Bean public CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache) { + CacheServerFactoryBean gemfireCacheServer = new CacheServerFactoryBean(); gemfireCacheServer.setCache(gemfireCache); + gemfireCacheServer.setCacheServerConfigurers(resolveCacheServerConfigurers()); gemfireCacheServer.setAutoStartup(autoStartup()); gemfireCacheServer.setBindAddress(bindAddress()); gemfireCacheServer.setHostNameForClients(hostnameForClients()); @@ -101,18 +131,40 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { return gemfireCacheServer; } + /* (non-Javadoc) */ + private List resolveCacheServerConfigurers() { + + return Optional.ofNullable(this.cacheServerConfigurers) + .filter(cacheServerConfigurers -> !cacheServerConfigurers.isEmpty()) + .orElseGet(() -> + Optional.of(this.beanFactory()) + .filter(beanFactory -> beanFactory instanceof ListableBeanFactory) + .map(beanFactory -> { + Map beansOfType = ((ListableBeanFactory) beanFactory) + .getBeansOfType(CacheServerConfigurer.class, true, true); + + return nullSafeMap(beansOfType).values().stream().collect(Collectors.toList()); + }) + .orElseGet(Collections::emptyList) + ); + + } + /** - * Configures GemFire {@link CacheServer} specific settings. + * Configures {@link CacheServer} specific settings. * - * @param importMetadata {@link AnnotationMetadata} containing cache server meta-data used to configure - * the GemFire {@link CacheServer}. + * @param importMetadata {@link AnnotationMetadata} containing cache server meta-data used to + * configure the {@link CacheServer}. * @see org.springframework.core.type.AnnotationMetadata + * @see org.apache.geode.cache.server.CacheServer */ @Override - protected void configureOther(AnnotationMetadata importMetadata) { + protected void configureTheRest(AnnotationMetadata importMetadata) { + super.configureCache(importMetadata); if (isCacheServerApplication(importMetadata)) { + Map cacheServerApplicationMetadata = importMetadata.getAnnotationAttributes(getAnnotationTypeName()); @@ -129,8 +181,8 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { setSocketBufferSize((Integer) cacheServerApplicationMetadata.get("socketBufferSize")); setSubscriptionCapacity((Integer) cacheServerApplicationMetadata.get("subscriptionCapacity")); setSubscriptionDiskStoreName((String) cacheServerApplicationMetadata.get("subscriptionDiskStoreName")); - setSubscriptionEvictionPolicy((SubscriptionEvictionPolicy) cacheServerApplicationMetadata.get( - "subscriptionEvictionPolicy")); + setSubscriptionEvictionPolicy((SubscriptionEvictionPolicy) + cacheServerApplicationMetadata.get("subscriptionEvictionPolicy")); } } @@ -157,7 +209,8 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected String bindAddress() { - return SpringUtils.defaultIfNull(this.bindAddress, CacheServer.DEFAULT_BIND_ADDRESS); + return Optional.ofNullable(this.bindAddress).filter(StringUtils::hasText) + .orElse(CacheServer.DEFAULT_BIND_ADDRESS); } /* (non-Javadoc) */ @@ -166,7 +219,8 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected String hostnameForClients() { - return SpringUtils.defaultIfNull(this.hostnameForClients, CacheServer.DEFAULT_HOSTNAME_FOR_CLIENTS); + return Optional.ofNullable(this.hostnameForClients).filter(StringUtils::hasText) + .orElse(CacheServer.DEFAULT_HOSTNAME_FOR_CLIENTS); } /* (non-Javadoc) */ @@ -175,7 +229,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected Set interestRegistrationListeners() { - return CollectionUtils.nullSafeSet(this.interestRegistrationListeners); + return nullSafeSet(this.interestRegistrationListeners); } /* (non-Javadoc) */ @@ -184,7 +238,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected Long loadPollInterval() { - return SpringUtils.defaultIfNull(this.loadPollInterval, CacheServer.DEFAULT_LOAD_POLL_INTERVAL); + return Optional.ofNullable(this.loadPollInterval).orElse(CacheServer.DEFAULT_LOAD_POLL_INTERVAL); } /* (non-Javadoc) */ @@ -193,7 +247,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected Integer maxConnections() { - return SpringUtils.defaultIfNull(this.maxConnections, CacheServer.DEFAULT_MAX_CONNECTIONS); + return Optional.ofNullable(this.maxConnections).orElse(CacheServer.DEFAULT_MAX_CONNECTIONS); } /* (non-Javadoc) */ @@ -202,7 +256,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected Integer maxMessageCount() { - return SpringUtils.defaultIfNull(this.maxMessageCount, CacheServer.DEFAULT_MAXIMUM_MESSAGE_COUNT); + return Optional.ofNullable(this.maxMessageCount).orElse(CacheServer.DEFAULT_MAXIMUM_MESSAGE_COUNT); } /* (non-Javadoc) */ @@ -211,7 +265,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected Integer maxThreads() { - return SpringUtils.defaultIfNull(this.maxThreads, CacheServer.DEFAULT_MAX_THREADS); + return Optional.ofNullable(this.maxThreads).orElse(CacheServer.DEFAULT_MAX_THREADS); } /* (non-Javadoc) */ @@ -220,7 +274,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected Integer maxTimeBetweenPings() { - return SpringUtils.defaultIfNull(this.maxTimeBetweenPings, CacheServer.DEFAULT_MAXIMUM_TIME_BETWEEN_PINGS); + return Optional.ofNullable(this.maxTimeBetweenPings).orElse(CacheServer.DEFAULT_MAXIMUM_TIME_BETWEEN_PINGS); } /* (non-Javadoc) */ @@ -229,7 +283,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected Integer messageTimeToLive() { - return SpringUtils.defaultIfNull(this.messageTimeToLive, CacheServer.DEFAULT_MESSAGE_TIME_TO_LIVE); + return Optional.ofNullable(this.messageTimeToLive).orElse(CacheServer.DEFAULT_MESSAGE_TIME_TO_LIVE); } /* (non-Javadoc) */ @@ -238,7 +292,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected Integer port() { - return SpringUtils.defaultIfNull(this.port, CacheServer.DEFAULT_PORT); + return Optional.ofNullable(this.port).orElse(CacheServer.DEFAULT_PORT); } /* (non-Javadoc) */ @@ -247,7 +301,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected ServerLoadProbe serverLoadProbe() { - return SpringUtils.defaultIfNull(this.serverLoadProbe, CacheServer.DEFAULT_LOAD_PROBE); + return Optional.ofNullable(this.serverLoadProbe).orElse(CacheServer.DEFAULT_LOAD_PROBE); } /* (non-Javadoc) */ @@ -256,7 +310,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected Integer socketBufferSize() { - return SpringUtils.defaultIfNull(this.socketBufferSize, CacheServer.DEFAULT_SOCKET_BUFFER_SIZE); + return Optional.ofNullable(this.socketBufferSize).orElse(CacheServer.DEFAULT_SOCKET_BUFFER_SIZE); } /* (non-Javadoc) */ @@ -265,7 +319,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected Integer subscriptionCapacity() { - return SpringUtils.defaultIfNull(this.subscriptionCapacity, ClientSubscriptionConfig.DEFAULT_CAPACITY); + return Optional.ofNullable(this.subscriptionCapacity).orElse(ClientSubscriptionConfig.DEFAULT_CAPACITY); } /* (non-Javadoc) */ @@ -283,7 +337,7 @@ public class CacheServerConfiguration extends PeerCacheConfiguration { } protected SubscriptionEvictionPolicy subscriptionEvictionPolicy() { - return SpringUtils.defaultIfNull(this.subscriptionEvictionPolicy, SubscriptionEvictionPolicy.DEFAULT); + return Optional.ofNullable(this.subscriptionEvictionPolicy).orElse(SubscriptionEvictionPolicy.DEFAULT); } @Override diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerConfigurer.java b/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerConfigurer.java new file mode 100644 index 00000000..ed67857d --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/CacheServerConfigurer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 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.config.annotation; + +import org.apache.geode.cache.server.CacheServer; +import org.springframework.data.gemfire.server.CacheServerFactoryBean; + +/** + * The {@link CacheServerConfigurer} interface defines a contract for implementations to customize the configuration + * of a {@link CacheServerFactoryBean} used to construct, configure and initialize an instance of a {@link CacheServer}. + * + * @author John Blum + * @see org.apache.geode.cache.server.CacheServer + * @see org.springframework.data.gemfire.config.annotation.CacheServerApplication + * @see org.springframework.data.gemfire.config.annotation.EnableCacheServers + * @see org.springframework.data.gemfire.config.annotation.EnableCacheServer + * @see org.springframework.data.gemfire.server.CacheServerFactoryBean + * @since 1.9.0 + */ +public interface CacheServerConfigurer { + + /** + * Configuration callback method providing a reference to a {@link CacheServerFactoryBean} used to construct, + * configure and initialize an instance of {@link CacheServer}. + * + * @param beanName name of {@link CacheServer} bean declared in the Spring application context. + * @param bean reference to the {@link CacheServerFactoryBean}. + * @see org.springframework.data.gemfire.server.CacheServerFactoryBean + */ + void configure(String beanName, CacheServerFactoryBean bean); + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.java index 264554ae..dea2dec8 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.java @@ -37,11 +37,11 @@ import org.springframework.data.gemfire.support.GemfireBeanFactoryLocator; * a GemFire cache client (i.e. {@link org.apache.geode.cache.client.ClientCache}). * * @author John Blum + * @see org.apache.geode.cache.client.PoolFactory + * @see org.apache.geode.cache.control.ResourceManager * @see org.springframework.context.annotation.Configuration * @see org.springframework.context.annotation.Import * @see org.springframework.data.gemfire.config.annotation.ClientCacheConfiguration - * @see org.apache.geode.cache.client.PoolFactory - * @see org.apache.geode.cache.control.ResourceManager * @since 1.9.0 */ @Target(ElementType.TYPE) @@ -195,7 +195,7 @@ public @interface ClientCacheApplication { int retryAttempts() default PoolFactory.DEFAULT_RETRY_ATTEMPTS; /** - * Configures the group that all servers this pool connects to must belong to. + * Configures the group that all servers in which this pool connects to must belong to. * * @see org.apache.geode.cache.client.PoolFactory#DEFAULT_SERVER_GROUP */ diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfiguration.java index 4eef2f22..1fca763a 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfiguration.java @@ -17,9 +17,19 @@ package org.springframework.data.gemfire.config.annotation; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.client.Pool; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.context.annotation.Bean; @@ -33,14 +43,24 @@ import org.springframework.data.gemfire.support.ConnectionEndpoint; import org.springframework.data.gemfire.support.ConnectionEndpointList; /** - * Spring {@link Configuration} class used to configure, construct and initialize - * a GemFire {@link org.apache.geode.cache.client.ClientCache} instance in a Spring application context. + * Spring {@link Configuration} class used to construct, configure and initialize + * a {@link org.apache.geode.cache.client.ClientCache} instance in a Spring application context. * * @author John Blum + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.client.Pool + * @see org.springframework.beans.factory.config.BeanDefinition + * @see org.springframework.beans.factory.support.BeanDefinitionBuilder * @see org.springframework.context.annotation.Bean * @see org.springframework.context.annotation.Configuration + * @see org.springframework.core.type.AnnotationMetadata + * @see org.springframework.data.gemfire.CacheFactoryBean + * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean * @see org.springframework.data.gemfire.config.annotation.AbstractCacheConfiguration - * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer + * @see org.springframework.data.gemfire.config.support.ClientRegionPoolBeanFactoryPostProcessor + * @see org.springframework.data.gemfire.support.ConnectionEndpoint + * @see org.springframework.data.gemfire.support.ConnectionEndpointList * @since 1.0.0 */ @Configuration @@ -78,16 +98,30 @@ public class ClientCacheConfiguration extends AbstractCacheConfiguration { private Iterable locators; private Iterable servers; + @Autowired(required = false) + private List clientCacheConfigurers = Collections.emptyList(); + private Long idleTimeout; private Long pingInterval; private String durableClientId; private String serverGroup; + /** + * Bean declaration for a single, peer {@link ClientCache} instance. + * + * @return a new instance of a peer {@link ClientCache}. + * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.GemFireCache + * @see #constructCacheFactoryBean() + */ @Bean public ClientCacheFactoryBean gemfireCache() { + ClientCacheFactoryBean gemfireCache = constructCacheFactoryBean(); + gemfireCache.setClientCacheConfigurers(resolveClientCacheConfigurers()); gemfireCache.setDurableClientId(durableClientId()); gemfireCache.setDurableClientTimeout(durableClientTimeout()); gemfireCache.setFreeConnectionTimeout(freeConnectionTimeout()); @@ -116,8 +150,30 @@ public class ClientCacheConfiguration extends AbstractCacheConfiguration { return gemfireCache; } + /* (non-Javadoc) */ + private List resolveClientCacheConfigurers() { + + return Optional.ofNullable(this.clientCacheConfigurers) + .filter(clientCacheConfigurers -> !clientCacheConfigurers.isEmpty()) + .orElseGet(() -> + Optional.of(this.beanFactory()) + .filter(beanFactory -> beanFactory instanceof ListableBeanFactory) + .map(beanFactory -> { + Map beansOfType = ((ListableBeanFactory) beanFactory) + .getBeansOfType(ClientCacheConfigurer.class, true, true); + + return nullSafeMap(beansOfType).values().stream().collect(Collectors.toList()); + }) + .orElseGet(Collections::emptyList) + ); + } + /** - * {@inheritDoc} + * Constructs a new instance of {@link ClientCacheFactoryBean} used to create a peer {@link ClientCache}. + * + * @param {@link Class} sub-type of {@link CacheFactoryBean}. + * @return a new instance of {@link ClientCacheFactoryBean}. + * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean */ @Override @SuppressWarnings("unchecked") @@ -126,16 +182,27 @@ public class ClientCacheConfiguration extends AbstractCacheConfiguration { } /** - * {@inheritDoc} + * Configures Spring container infrastructure components and beans used by Spring Data GemFire + * to enable Pivotal GemFire or Apache Geode to function properly inside a Spring context. + * + * This overridden method configures and registers additional Spring components and bean applicable to + * {@link ClientCache ClientCaches}. + * + * @param importMetadata {@link AnnotationMetadata} containing annotation meta-data + * for the Spring Data GemFire cache application class. + * @see org.springframework.core.type.AnnotationMetadata */ @Override protected void configureInfrastructure(AnnotationMetadata importMetadata) { + super.configureInfrastructure(importMetadata); + registerClientRegionPoolBeanFactoryPostProcessor(importMetadata); } /* (non-Javadoc) */ - protected void registerClientRegionPoolBeanFactoryPostProcessor(AnnotationMetadata importMetadata) { + private void registerClientRegionPoolBeanFactoryPostProcessor(AnnotationMetadata importMetadata) { + if (CLIENT_REGION_POOL_BEAN_FACTORY_POST_PROCESSOR_REGISTERED.compareAndSet(false, true)) { register(BeanDefinitionBuilder.rootBeanDefinition(ClientRegionPoolBeanFactoryPostProcessor.class) .setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition()); @@ -143,17 +210,20 @@ public class ClientCacheConfiguration extends AbstractCacheConfiguration { } /** - * Configures GemFire {@link org.apache.geode.cache.client.ClientCache} specific settings. + * Configures {@link ClientCache} specific settings. * - * @param importMetadata {@link AnnotationMetadata} containing client cache meta-data used to configure - * the GemFire {@link org.apache.geode.cache.client.ClientCache}. + * @param importMetadata {@link AnnotationMetadata} containing client cache meta-data used to + * configure the {@link ClientCache}. * @see org.springframework.core.type.AnnotationMetadata + * @see #configureLocatorsAndServers(Map) */ @Override protected void configureCache(AnnotationMetadata importMetadata) { + super.configureCache(importMetadata); if (isClientCacheApplication(importMetadata)) { + Map clientCacheApplicationAttributes = importMetadata.getAnnotationAttributes(getAnnotationTypeName()); @@ -185,16 +255,16 @@ public class ClientCacheConfiguration extends AbstractCacheConfiguration { } /** - * Uses the list of GemFire Locator and Server connection endpoint definitions and meta-data to configure - * the GemFire client {@link org.apache.geode.cache.client.Pool} used to communicate with the servers - * in the GemFire cluster. + * Uses the list of Pivotal GemFire/Apache Geode Locator and Server connection endpoint definitions and meta-data + * to configure the client {@link Pool} used to communicate with the servers in the cluster. * - * @param clientCacheApplicationAttributes {@link ClientCacheApplication} annotation containing - * {@link org.apache.geode.cache.client.Pool} Locator/Server connection endpoint meta-data. + * @param clientCacheApplicationAttributes {@link ClientCacheApplication} annotation containing {@link Pool} + * Locator/Server connection endpoint meta-data. * @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication * @see java.util.Map */ - protected void configureLocatorsAndServers(Map clientCacheApplicationAttributes) { + private void configureLocatorsAndServers(Map clientCacheApplicationAttributes) { + ConnectionEndpointList poolLocators = new ConnectionEndpointList(); AnnotationAttributes[] locators = (AnnotationAttributes[]) clientCacheApplicationAttributes.get("locators"); diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfigurer.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfigurer.java new file mode 100644 index 00000000..d44214f7 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfigurer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016 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.config.annotation; + +import org.apache.geode.cache.client.ClientCache; +import org.springframework.data.gemfire.client.ClientCacheFactoryBean; + +/** + * The {@link ClientCacheConfigurer} interface defines a contract for implementations to customize the configuration + * of a {@link ClientCacheFactoryBean} used to construct, configure and initialize an instance of a {@link ClientCache}. + * + * @author John Blum + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean + * @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication + * @since 1.9.0 + */ +public interface ClientCacheConfigurer { + + /** + * Configuration callback method providing a reference to a {@link ClientCacheFactoryBean} used to construct, + * configure and initialize an instance of {@link ClientCache}. + * + * @param beanName name of {@link ClientCache} bean declared in the Spring application context. + * @param bean reference to the {@link ClientCacheFactoryBean}. + * @see org.springframework.data.gemfire.CacheFactoryBean + */ + void configure(String beanName, ClientCacheFactoryBean bean); + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfiguration.java index beb13bd4..db9d2e26 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfiguration.java @@ -17,6 +17,19 @@ package org.springframework.data.gemfire.config.annotation; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -40,17 +53,25 @@ import org.springframework.data.gemfire.util.ArrayUtils; * @see org.springframework.data.gemfire.DiskStoreFactoryBean * @see org.springframework.data.gemfire.config.annotation.EnableDiskStore * @see org.springframework.data.gemfire.config.annotation.EnableDiskStores + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfigurer * @see org.apache.geode.cache.DiskStore * @since 1.9.0 */ -public class DiskStoreConfiguration implements ImportBeanDefinitionRegistrar { +public class DiskStoreConfiguration implements BeanFactoryAware, ImportBeanDefinitionRegistrar { + + private BeanFactory beanFactory; + + @Autowired(required = false) + private List diskStoreConfigurers = Collections.emptyList(); /** * @inheritDoc */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (importingClassMetadata.hasAnnotation(EnableDiskStore.class.getName())) { + AnnotationAttributes enableDiskStoreAttributes = AnnotationAttributes.fromMap( importingClassMetadata.getAnnotationAttributes(EnableDiskStore.class.getName())); @@ -71,6 +92,8 @@ public class DiskStoreConfiguration implements ImportBeanDefinitionRegistrar { diskStoreFactoryBeanBuilder.addPropertyReference("cache", GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME); + diskStoreFactoryBeanBuilder.addPropertyValue("diskStoreConfigurers", resolveDiskStoreConfigurers()); + setPropertyValueIfNotDefault(diskStoreFactoryBeanBuilder, "allowForceCompaction", enableDiskStoreAttributes.getBoolean("allowForceCompaction"), false); @@ -103,6 +126,24 @@ public class DiskStoreConfiguration implements ImportBeanDefinitionRegistrar { registry.registerBeanDefinition(diskStoreName, diskStoreFactoryBeanBuilder.getBeanDefinition()); } + /* (non-Javadoc) */ + private List resolveDiskStoreConfigurers() { + + return Optional.ofNullable(this.diskStoreConfigurers) + .filter(diskStoreConfigurers -> !diskStoreConfigurers.isEmpty()) + .orElseGet(() -> + Optional.of(this.beanFactory) + .filter(beanFactory -> beanFactory instanceof ListableBeanFactory) + .map(beanFactory -> { + Map beansOfType = ((ListableBeanFactory) beanFactory) + .getBeansOfType(DiskStoreConfigurer.class, true, true); + + return nullSafeMap(beansOfType).values().stream().collect(Collectors.toList()); + }) + .orElseGet(Collections::emptyList) + ); + } + /* (non-Javadoc) */ protected BeanDefinitionBuilder parseDiskStoreDiskDirectories(AnnotationMetadata importingClassMetadata, AnnotationAttributes enableDiskStoreAttributes, BeanDefinitionBuilder diskStoreBeanFactoryBuilder) { @@ -131,9 +172,14 @@ public class DiskStoreConfiguration implements ImportBeanDefinitionRegistrar { /* (non-Javadoc) */ private BeanDefinitionBuilder setPropertyValueIfNotDefault(BeanDefinitionBuilder beanDefinitionBuilder, - String propertyName, T value, T defaultValue) { + String propertyName, T value, T defaultValue) { return (value != null && !value.equals(defaultValue) ? beanDefinitionBuilder.addPropertyValue(propertyName, value) : beanDefinitionBuilder); } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfigurer.java b/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfigurer.java new file mode 100644 index 00000000..2f2b37be --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfigurer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 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.config.annotation; + +import org.apache.geode.cache.DiskStore; +import org.springframework.data.gemfire.DiskStoreFactoryBean; + +/** + * The {@link DiskStoreConfigurer} interface defines a contract for implementations to customize the configuration + * of a {@link DiskStoreFactoryBean} used to construct, configure and initialize a {@link DiskStore}. + * + * @author John Blum + * @see org.apache.geode.cache.DiskStore + * @see org.springframework.data.gemfire.DiskStoreFactoryBean + * @see org.springframework.data.gemfire.config.annotation.EnableDiskStore + * @see org.springframework.data.gemfire.config.annotation.EnableDiskStores + * @since 1.1.0 + */ +public interface DiskStoreConfigurer { + + /** + * Configuration callback method providing a reference to a {@link DiskStoreFactoryBean} used to construct, + * configure and initialize an instance of {@link DiskStore}. + * + * @param beanName name of the {@link DiskStore} bean declared in the Spring application context. + * @param bean reference to the {@link DiskStoreFactoryBean}. + * @see org.springframework.data.gemfire.DiskStoreFactoryBean + */ + void configure(String beanName, DiskStoreFactoryBean bean); + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCacheServer.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCacheServer.java index 00c92f6d..e80aeb17 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCacheServer.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCacheServer.java @@ -41,8 +41,10 @@ import org.springframework.data.gemfire.server.SubscriptionEvictionPolicy; * the {@link EnableCacheServers} annotation. * @author John Blum - * @see org.springframework.data.gemfire.config.annotation.AddCacheServerConfiguration * @see org.apache.geode.cache.server.CacheServer + * @see org.springframework.data.gemfire.config.annotation.AddCacheServerConfiguration + * @see org.springframework.data.gemfire.config.annotation.CacheServerConfigurer + * @see org.springframework.data.gemfire.config.annotation.EnableCacheServers * @since 1.9.0 */ @Target(ElementType.TYPE) @@ -117,6 +119,13 @@ public @interface EnableCacheServer { */ int messageTimeToLive() default CacheServer.DEFAULT_MESSAGE_TIME_TO_LIVE; + /** + * Configures the name of the Spring bean defined in the Spring application context. + * + * Defaults to empty. + */ + String name() default ""; + /** * Configures the port on which this cache server listens for clients. * diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCacheServers.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCacheServers.java index 3da0b0ed..85b7ea4e 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCacheServers.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableCacheServers.java @@ -24,14 +24,17 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.apache.geode.cache.server.CacheServer; import org.springframework.context.annotation.Import; /** - * The {@link EnableCacheServers} annotation enables 1 or more GemFire {@link org.apache.geode.cache.server.CacheServer CacheServers} - * to be defined and used in a GemFire peer cache application configured with Spring (Data GemFire). + * The {@link EnableCacheServers} annotation enables 1 or more {@link CacheServer CacheServers} + * to be defined and used in a peer cache application configured with Spring (Data GemFire/Geode). * * @author John Blum + * @see org.apache.geode.cache.server.CacheServer * @see org.springframework.data.gemfire.config.annotation.AddCacheServersConfiguration + * @see org.springframework.data.gemfire.config.annotation.CacheServerConfigurer * @see org.springframework.data.gemfire.config.annotation.EnableCacheServer * @since 1.9.0 */ diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStore.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStore.java index e060a09a..b88378c0 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStore.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStore.java @@ -34,12 +34,13 @@ import org.springframework.core.annotation.AliasFor; * {@link org.apache.geode.cache.Region Regions} * * @author John Blum + * @see org.apache.geode.cache.DiskStore + * @see org.apache.geode.cache.Region * @see org.springframework.context.annotation.Import * @see org.springframework.core.annotation.AliasFor * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfiguration + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfigurer * @see org.springframework.data.gemfire.config.annotation.EnableDiskStores - * @see org.apache.geode.cache.DiskStore - * @see org.apache.geode.cache.Region * @since 1.9.0 */ @Target(ElementType.TYPE) diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStores.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStores.java index 9aa1612c..bcaaff13 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStores.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableDiskStores.java @@ -33,11 +33,12 @@ import org.springframework.context.annotation.Import; * {@link org.apache.geode.cache.Region Regions} * * @author John Blum - * @see org.springframework.context.annotation.Import - * @see org.springframework.data.gemfire.config.annotation.DiskStoresConfiguration - * @see org.springframework.data.gemfire.config.annotation.EnableDiskStore * @see org.apache.geode.cache.DiskStore * @see org.apache.geode.cache.Region + * @see org.springframework.context.annotation.Import + * @see org.springframework.data.gemfire.config.annotation.DiskStoresConfiguration + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfigurer + * @see org.springframework.data.gemfire.config.annotation.EnableDiskStore * @since 1.9.0 */ @Target(ElementType.TYPE) diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegions.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegions.java index 6d37bafb..9fc0ef41 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegions.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEntityDefinedRegions.java @@ -26,21 +26,26 @@ import java.lang.annotation.Target; import org.apache.geode.cache.Region; import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.annotation.AliasFor; /** - * The {@link EnableEntityDefinedRegions} annotation marks a Spring {@link org.springframework.context.annotation.Configuration @Configuration} + * The {@link EnableEntityDefinedRegions} annotation marks a Spring {@link Configuration @Configuration} application * annotated class to enable the creation of the GemFire/Geode {@link Region Regions} based on - * the application domain model object entities. + * the application persistent entities. * * @author John Blum + * @see org.apache.geode.cache.Region * @see org.springframework.context.annotation.ComponentScan * @see org.springframework.context.annotation.ComponentScan.Filter * @see org.springframework.context.annotation.Import * @see org.springframework.core.annotation.AliasFor + * @see org.springframework.data.gemfire.RegionFactoryBean + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean * @see org.springframework.data.gemfire.config.annotation.EntityDefinedRegionsConfiguration - * @see org.apache.geode.cache.Region + * @see org.springframework.data.gemfire.config.annotation.IndexConfiguration + * @see org.springframework.data.gemfire.config.annotation.support.GemFireCacheTypeAwareRegionFactoryBean * @since 1.9.0 */ @Target(ElementType.TYPE) @@ -62,7 +67,10 @@ public @interface EnableEntityDefinedRegions { /** * Base packages to scan for {@link org.springframework.data.gemfire.mapping.annotation.Region @Region} annotated - * application persistent entities. {@link #value()} is an alias for this attribute. + * application persistent entities. + * + * The {@link #value()} attribute is an alias for this attribute. + * * Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. * * @return a {@link String} array specifying the packages to search for application persistent entities. @@ -72,10 +80,13 @@ public @interface EnableEntityDefinedRegions { String[] basePackages() default {}; /** - * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for + * Type-safe alternative to the {@link #basePackages()} attribute for specifying the packages to scan for * {@link org.springframework.data.gemfire.mapping.annotation.Region @Region} annotated application persistent entities. - * The package of each class specified will be scanned. Consider creating a special no-op marker class or interface - * in each package that serves no other purpose than being referenced by this attribute. + * + * The package of each class specified will be scanned. + * + * Consider creating a special no-op marker class or interface in each package that serves no other purpose + * than being referenced by this attribute. * * @return an array of {@link Class classes} used to determine the packages to scan * for application persistent entities. @@ -91,12 +102,13 @@ public @interface EnableEntityDefinedRegions { ComponentScan.Filter[] excludeFilters() default {}; /** - * Specifies which types are eligible for component scanning. Further narrows the set of candidate components - * from everything in {@link #basePackages()} to everything in the base packages that matches the given filter - * or filters. + * Specifies which types are eligible for component scanning. * - * @return an array {@link org.springframework.context.annotation.ComponentScan.Filter} of Filters used to - * specify application persistent entities to be included during the component scan. + * Further narrows the set of candidate components from everything in {@link #basePackages()} + * or {@link #basePackageClasses()} to everything in the base packages that matches the given filter or filters. + * + * @return an array {@link ComponentScan.Filter} of Filters used to specify application persistent entities + * to be included during the component scan. */ ComponentScan.Filter[] includeFilters() default {}; diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEviction.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEviction.java index 17d3ed27..01aa6bd9 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEviction.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableEviction.java @@ -73,7 +73,7 @@ public @interface EnableEviction { * * Defaults to {@link EvictionActionType#LOCAL_DESTROY}. * - * @see EvictionActionType + * @see org.springframework.data.gemfire.eviction.EvictionActionType */ EvictionActionType action() default EvictionActionType.LOCAL_DESTROY; @@ -85,8 +85,8 @@ public @interface EnableEviction { int maximum() default EvictionAttributes.DEFAULT_ENTRIES_MAXIMUM; /** - * Name of a Spring bean of type {@link ObjectSizer} defined in the Spring context used - * to size {@link Region} entry values. + * Name of a Spring bean of type {@link ObjectSizer} defined in the Spring application context + * used to size {@link Region} entry values. * * Defaults to empty. * @@ -95,14 +95,14 @@ public @interface EnableEviction { String objectSizerName() default ""; /** - * Names of {@link Region Regions} for which this Eviction policy applies. + * Names of all the {@link Region Regions} in which this Eviction policy will be applied. * * Defaults to empty. */ String[] regionNames() default {}; /** - * Eviction alorithm used during Eviction. + * Eviction algorithm used during Eviction. * * Defaults to {@link EvictionPolicyType#ENTRY_COUNT}. * diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableExpiration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableExpiration.java index 0db42159..b45414ac 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableExpiration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableExpiration.java @@ -133,6 +133,7 @@ public @interface EnableExpiration { * @see Geode Expiration */ enum ExpirationType { + IDLE_TIMEOUT("TTI"), TIME_TO_LIVE("TTL"); diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableIndexing.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableIndexing.java index b015935c..08e26595 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnableIndexing.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnableIndexing.java @@ -24,16 +24,23 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.apache.geode.cache.lucene.LuceneIndex; import org.apache.geode.cache.query.Index; +import org.springframework.context.annotation.Configuration; /** - * The {@link EnableIndexing} annotation marks a Spring {@link org.springframework.context.annotation.Configuration @Configuration} - * annotated application class to enable the creation of GemFire/Geode Indexes based on application persistent entity - * field/property annotations, such as the {@link @Id}, {@link @Indexed} and {@link @LuceneIndex} annotations. + * The {@link EnableIndexing} annotation marks a Spring {@link Configuration @Configuration} annotated application class + * to enable the creation of GemFire/Geode {@link Index Indexes} and {@link LuceneIndex LuceneIndexes} based on + * application persistent entity field/property annotations, such as the {@link @Id}, {@link @Indexed} + * and {@link @LuceneIndex} annotations. * * @author John Blum - * @see org.springframework.data.gemfire.config.annotation.IndexConfiguration + * @see org.apache.geode.cache.lucene.LuceneIndex * @see org.apache.geode.cache.query.Index + * @see org.springframework.data.gemfire.IndexFactoryBean + * @see org.springframework.data.gemfire.config.annotation.IndexConfiguration + * @see org.springframework.data.gemfire.config.annotation.IndexConfigurer + * @see org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean * @since 1.9.0 */ @Target(ElementType.TYPE) @@ -46,9 +53,12 @@ public @interface EnableIndexing { /** * Determines whether all GemFire/Geode {@link Index Indexes} will be defined before created. * If set to {@literal true}, then all {@link Index Indexes} are defined first and the created - * in a single, bulk operation, thereby improving index creation efficiency. + * in a single, bulk operation, thereby improving {@link Index} creation process efficiency. * - * Defaults to false. + * Only applies to OQL-based {@link Index Indexes}. {@link LuceneIndex LuceneIndexes} are managed differently + * by GemFire/Geode. + * + * Defaults to {@literal false}. */ boolean define() default false; diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePool.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePool.java index 1294d60b..6ad19f45 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePool.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePool.java @@ -40,9 +40,11 @@ import org.springframework.data.gemfire.GemfireUtils; * annotation. * * @author John Blum - * @see org.springframework.data.gemfire.config.annotation.AddPoolConfiguration * @see org.apache.geode.cache.client.Pool * @see org.apache.geode.cache.client.PoolFactory + * @see org.springframework.data.gemfire.config.annotation.AddPoolConfiguration + * @see org.springframework.data.gemfire.config.annotation.EnablePools + * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer * @since 1.9.0 */ @Target(ElementType.TYPE) diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePools.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePools.java index 94054d83..f7fb891a 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePools.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EnablePools.java @@ -33,6 +33,7 @@ import org.springframework.context.annotation.Import; * @author John Blum * @see org.springframework.data.gemfire.config.annotation.AddPoolsConfiguration * @see org.springframework.data.gemfire.config.annotation.EnablePool + * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer * @since 1.9.0 */ @Target(ElementType.TYPE) diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EntityDefinedRegionsConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EntityDefinedRegionsConfiguration.java index 7eda715b..f3cdb35f 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EntityDefinedRegionsConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EntityDefinedRegionsConfiguration.java @@ -20,15 +20,20 @@ package org.springframework.data.gemfire.config.annotation; import static java.util.Arrays.stream; import static org.springframework.data.gemfire.util.ArrayUtils.defaultIfEmpty; import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; import java.lang.annotation.Annotation; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.apache.geode.cache.Region; import org.apache.geode.cache.client.ClientRegionShortcut; @@ -37,6 +42,8 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -82,7 +89,9 @@ import org.springframework.util.StringUtils; * based on the application persistent entity classes. * * @author John Blum + * @see java.lang.ClassLoader * @see java.lang.annotation.Annotation + * @see org.apache.geode.cache.Region * @see org.springframework.beans.factory.BeanClassLoaderAware * @see org.springframework.beans.factory.BeanFactory * @see org.springframework.beans.factory.BeanFactoryAware @@ -106,7 +115,6 @@ import org.springframework.util.StringUtils; * @see org.springframework.data.gemfire.mapping.annotation.PartitionRegion * @see org.springframework.data.gemfire.mapping.annotation.ReplicateRegion * @see org.springframework.data.gemfire.mapping.annotation.Region - * @see org.apache.geode.cache.Region * @since 1.9.0 */ @SuppressWarnings("unused") @@ -134,6 +142,9 @@ public class EntityDefinedRegionsConfiguration private GemfireMappingContext mappingContext; + @Autowired(required = false) + private List regionConfigurers = Collections.emptyList(); + /** * Returns the {@link Annotation} {@link Class type} that configures and creates {@link Region Regions} * for application persistent entities. @@ -231,9 +242,9 @@ public class EntityDefinedRegionsConfiguration /* (non-Javadoc) */ protected GemfirePersistentEntity getPersistentEntity(Class persistentEntityType) { + return resolveMappingContext().getPersistentEntity(persistentEntityType).orElseThrow( - () -> new IllegalStateException(String.format("PersistentEntity for type [%s] not found", - persistentEntityType))); + () -> newIllegalStateException("PersistentEntity for type [%s] not found", persistentEntityType)); } /* (non-Javadoc) */ @@ -255,19 +266,21 @@ public class EntityDefinedRegionsConfiguration */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (isAnnotationPresent(importingClassMetadata)) { + AnnotationAttributes enableEntityDefinedRegionsAttributes = getAnnotationAttributes(importingClassMetadata); boolean strict = enableEntityDefinedRegionsAttributes.getBoolean("strict"); - for (Class persistentEntityClass : newGemFireComponentClassTypeScanner( - importingClassMetadata, enableEntityDefinedRegionsAttributes).scan()) { + newGemFireComponentClassTypeScanner(importingClassMetadata, enableEntityDefinedRegionsAttributes).scan() + .forEach(persistentEntityClass -> { - GemfirePersistentEntity persistentEntity = getPersistentEntity(persistentEntityClass); + GemfirePersistentEntity persistentEntity = getPersistentEntity(persistentEntityClass); - registerRegionBeanDefinition(persistentEntity, strict, registry); - postProcess(importingClassMetadata, registry, persistentEntity); - } + registerRegionBeanDefinition(persistentEntity, strict, registry); + postProcess(importingClassMetadata, registry, persistentEntity); + }); } } @@ -275,8 +288,8 @@ public class EntityDefinedRegionsConfiguration protected GemFireComponentClassTypeScanner newGemFireComponentClassTypeScanner( AnnotationMetadata importingClassMetadata, AnnotationAttributes enableEntityDefinedRegionsAttributes) { - Set resolvedBasePackages = resolveBasePackages(importingClassMetadata, - enableEntityDefinedRegionsAttributes); + Set resolvedBasePackages = + resolveBasePackages(importingClassMetadata, enableEntityDefinedRegionsAttributes); return GemFireComponentClassTypeScanner.from(resolvedBasePackages).with(resolveBeanClassLoader()) .withExcludes(resolveExcludes(enableEntityDefinedRegionsAttributes)) @@ -324,6 +337,7 @@ public class EntityDefinedRegionsConfiguration /* (non-Javadoc) */ private Iterable parseFilters(AnnotationAttributes[] componentScanFilterAttributes) { + Set typeFilters = new HashSet<>(); stream(nullSafeArray(componentScanFilterAttributes, AnnotationAttributes.class)) @@ -335,43 +349,45 @@ public class EntityDefinedRegionsConfiguration /* (non-Javadoc) */ @SuppressWarnings("unchecked") private Iterable typeFiltersFor(AnnotationAttributes filterAttributes) { + Set typeFilters = new HashSet<>(); FilterType filterType = filterAttributes.getEnum("type"); - for (Class filterClass : nullSafeArray(filterAttributes.getClassArray("value"), Class.class)) { - switch (filterType) { - case ANNOTATION: - Assert.isAssignable(Annotation.class, filterClass, - String.format("@ComponentScan.Filter class [%s] must be an Annotation", filterClass)); - typeFilters.add(new AnnotationTypeFilter((Class) filterClass)); - break; - case ASSIGNABLE_TYPE: - typeFilters.add(new AssignableTypeFilter(filterClass)); - break; - case CUSTOM: - Assert.isAssignable(TypeFilter.class, filterClass, - String.format("@ComponentScan.Filter class [%s] must be a TypeFilter", filterClass)); - typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class)); - break; - default: - throw new IllegalArgumentException(String.format( - "Illegal filter type [%s] when 'value' or 'classes' are specified", filterType)); - } - - for (String pattern : nullSafeGetPatterns(filterAttributes)) { + stream(nullSafeArray(filterAttributes.getClassArray("value"), Class.class)) + .forEach(filterClass -> { switch (filterType) { - case ASPECTJ: - typeFilters.add(new AspectJTypeFilter(pattern, resolveBeanClassLoader())); + case ANNOTATION: + Assert.isAssignable(Annotation.class, filterClass, + String.format("@ComponentScan.Filter class [%s] must be an Annotation", filterClass)); + typeFilters.add(new AnnotationTypeFilter((Class) filterClass)); break; - case REGEX: - typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(pattern))); + case ASSIGNABLE_TYPE: + typeFilters.add(new AssignableTypeFilter(filterClass)); + break; + case CUSTOM: + Assert.isAssignable(TypeFilter.class, filterClass, + String.format("@ComponentScan.Filter class [%s] must be a TypeFilter", filterClass)); + typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class)); break; default: - throw new IllegalArgumentException(String.format( - "Illegal filter type [%s] when 'patterns' are specified", filterType)); + throw newIllegalArgumentException( + "Illegal filter type [%s] when 'value' or 'classes' are specified", filterType); } - } - } + + for (String pattern : nullSafeGetPatterns(filterAttributes)) { + switch (filterType) { + case ASPECTJ: + typeFilters.add(new AspectJTypeFilter(pattern, resolveBeanClassLoader())); + break; + case REGEX: + typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(pattern))); + break; + default: + throw newIllegalArgumentException( + "Illegal filter type [%s] when 'patterns' are specified", filterType); + } + } + }); return typeFilters; } @@ -395,6 +411,7 @@ public class EntityDefinedRegionsConfiguration /* (non-Javadoc) */ @SuppressWarnings("unchecked") protected Iterable regionAnnotatedPersistentEntityTypeFilters() { + Set regionAnnotatedPersistentEntityTypeFilters = new HashSet<>(); org.springframework.data.gemfire.mapping.annotation.Region.REGION_ANNOTATION_TYPES.forEach( @@ -410,6 +427,7 @@ public class EntityDefinedRegionsConfiguration BeanDefinitionBuilder regionFactoryBeanBuilder = BeanDefinitionBuilder.genericBeanDefinition(resolveRegionFactoryBeanClass(persistentEntity)) .addPropertyReference("cache", GemfireConstants.DEFAULT_GEMFIRE_CACHE_NAME) + .addPropertyValue("regionConfigurers", resolveRegionConfigurers()) .addPropertyValue("close", false); setRegionAttributes(persistentEntity, regionFactoryBeanBuilder, strict); @@ -418,6 +436,24 @@ public class EntityDefinedRegionsConfiguration regionFactoryBeanBuilder.getBeanDefinition()); } + /* (non-Javadoc) */ + private List resolveRegionConfigurers() { + + return Optional.ofNullable(this.regionConfigurers) + .filter(regionConfigurers -> !regionConfigurers.isEmpty()) + .orElseGet(() -> + Optional.ofNullable(this.beanFactory) + .filter(beanFactory -> beanFactory instanceof ListableBeanFactory) + .map(beanFactory -> { + Map beansOfType = ((ListableBeanFactory) beanFactory) + .getBeansOfType(RegionConfigurer.class, true, true); + + return nullSafeMap(beansOfType).values().stream().collect(Collectors.toList()); + }) + .orElseGet(Collections::emptyList) + ); + } + /* (non-Javadoc) */ @SuppressWarnings("unchecked") protected Class resolveRegionFactoryBeanClass( @@ -493,9 +529,10 @@ public class EntityDefinedRegionsConfiguration /* (non-Javadoc) */ @SuppressWarnings("unchecked") protected Class resolveIdType(GemfirePersistentEntity persistentEntity) { + return (Class) persistentEntity.getIdProperty() .map(idProperty -> ((GemfirePersistentProperty) idProperty).getActualType()) - .orElse(Object.class); + .orElse(Object.class); } /* (non-Javadoc) */ @@ -571,6 +608,7 @@ public class EntityDefinedRegionsConfiguration "fixedPartitions", PartitionRegion.FixedPartition.class), PartitionRegion.FixedPartition.class); if (!ObjectUtils.isEmpty(fixedPartitions)) { + ManagedList fixedPartitionAttributesFactoryBeans = new ManagedList<>(fixedPartitions.length); diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/EvictionConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/EvictionConfiguration.java index 491b2310..a52a9e6b 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/EvictionConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/EvictionConfiguration.java @@ -18,17 +18,24 @@ package org.springframework.data.gemfire.config.annotation; import static org.springframework.data.gemfire.config.annotation.EnableEviction.EvictionPolicy; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; import org.apache.geode.cache.EvictionAttributes; import org.apache.geode.cache.Region; import org.apache.geode.cache.util.ObjectSizer; +import org.apache.shiro.util.Assert; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; @@ -44,14 +51,11 @@ import org.springframework.data.gemfire.client.ClientRegionFactoryBean; import org.springframework.data.gemfire.eviction.EvictionActionType; import org.springframework.data.gemfire.eviction.EvictionAttributesFactoryBean; import org.springframework.data.gemfire.eviction.EvictionPolicyType; -import org.springframework.data.gemfire.util.ArrayUtils; -import org.springframework.data.gemfire.util.CollectionUtils; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * The {@link EvictionConfiguration} class is a Spring {@link Configuration @Configuration} annotated class to enable - * Eviction policy configuration on GemFire/Geode {@link Region Regions}. + * Eviction policy configuration on cache {@link Region Regions}. * * @author John Blum * @see org.springframework.beans.factory.config.BeanPostProcessor @@ -76,19 +80,6 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa private EvictionPolicyConfigurer evictionPolicyConfigurer; - /** - * Determines whether the Spring bean is an instance of {@link RegionFactoryBean} - * or {@link ClientRegionFactoryBean}. - * - * @param bean Spring bean to evaluate. - * @return a boolean value indicating whether the Spring bean is an instance of {@link RegionFactoryBean}. - * @see org.springframework.data.gemfire.RegionFactoryBean - * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean - */ - protected static boolean isRegionFactoryBean(Object bean) { - return (bean instanceof RegionFactoryBean || bean instanceof ClientRegionFactoryBean); - } - /** * Returns the {@link Annotation} {@link Class type} that enables and configures Eviction. * @@ -124,34 +115,52 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa } /** - * @inheritDoc + * Sets a reference to the Spring {@link ApplicationContext}. + * + * @param applicationContext Spring {@link ApplicationContext} in use. + * @throws BeansException if an error occurs while storing a reference to the Spring {@link ApplicationContext}. + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(ApplicationContext) + * @see org.springframework.context.ApplicationContext */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } + /** + * Determines whether the Spring bean is an instance of {@link RegionFactoryBean} + * or {@link ClientRegionFactoryBean}. + * + * @param bean Spring bean to evaluate. + * @return a boolean value indicating whether the Spring bean is an instance of {@link RegionFactoryBean} + * or the {@link ClientRegionFactoryBean}. + * @see org.springframework.data.gemfire.RegionFactoryBean + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean + */ + protected static boolean isRegionFactoryBean(Object bean) { + return (bean instanceof RegionFactoryBean || bean instanceof ClientRegionFactoryBean); + } + /** * @inheritDoc */ @Override public void setImportMetadata(AnnotationMetadata importMetadata) { + if (importMetadata.hasAnnotation(getAnnotationTypeName())) { Map enableEvictionAttributes = importMetadata.getAnnotationAttributes(getAnnotationTypeName()); AnnotationAttributes[] policies = (AnnotationAttributes[]) enableEvictionAttributes.get("policies"); - for (AnnotationAttributes evictionPolicyAttributes - : ArrayUtils.nullSafeArray(policies, AnnotationAttributes.class)) { - + for (AnnotationAttributes evictionPolicyAttributes : nullSafeArray(policies, AnnotationAttributes.class)) { this.evictionPolicyConfigurer = ComposableEvictionPolicyConfigurer.compose( this.evictionPolicyConfigurer, EvictionPolicyMetaData.from(evictionPolicyAttributes, this.applicationContext)); } - this.evictionPolicyConfigurer = (this.evictionPolicyConfigurer != null ? this.evictionPolicyConfigurer - : EvictionPolicyMetaData.fromDefaults()); + this.evictionPolicyConfigurer = Optional.ofNullable(this.evictionPolicyConfigurer) + .orElseGet(EvictionPolicyMetaData::fromDefaults); } } @@ -163,16 +172,16 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa * @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration.EvictionPolicyConfigurer */ protected EvictionPolicyConfigurer getEvictionPolicyConfigurer() { - Assert.state(this.evictionPolicyConfigurer != null, - "EvictionPolicyConfigurer was not properly configured and initialized"); - - return this.evictionPolicyConfigurer; + return Optional.ofNullable(this.evictionPolicyConfigurer).orElseThrow(() -> + newIllegalStateException("EvictionPolicyConfigurer was not properly configured and initialized")); } @Bean @SuppressWarnings("unused") public BeanPostProcessor evictionBeanPostProcessor() { + return new BeanPostProcessor() { + @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return (isRegionFactoryBean(bean) ? getEvictionPolicyConfigurer().configure(bean) : bean); @@ -228,7 +237,7 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa */ @SuppressWarnings("unused") protected static EvictionPolicyConfigurer compose(EvictionPolicyConfigurer[] array) { - return compose(Arrays.asList(ArrayUtils.nullSafeArray(array, EvictionPolicyConfigurer.class))); + return compose(Arrays.asList(nullSafeArray(array, EvictionPolicyConfigurer.class))); } /** @@ -242,9 +251,10 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa * @see #compose(EvictionPolicyConfigurer, EvictionPolicyConfigurer) */ protected static EvictionPolicyConfigurer compose(Iterable iterable) { + EvictionPolicyConfigurer current = null; - for (EvictionPolicyConfigurer evictionPolicyConfigurer : CollectionUtils.nullSafeIterable(iterable)) { + for (EvictionPolicyConfigurer evictionPolicyConfigurer : nullSafeIterable(iterable)) { current = compose(current, evictionPolicyConfigurer); } @@ -291,26 +301,28 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa private final EvictionAttributes evictionAttributes; - private final Set regionNames = new HashSet(); + private final Set regionNames = new HashSet<>(); protected static EvictionPolicyMetaData from(AnnotationAttributes evictionPolicyAttributes, ApplicationContext applicationContext) { - return from((Integer) evictionPolicyAttributes.get("maximum"), - evictionPolicyAttributes.getEnum("type"), - evictionPolicyAttributes.getEnum("action"), - resolveObjectSizer(evictionPolicyAttributes.getString("objectSizerName"), applicationContext), - evictionPolicyAttributes.getStringArray("regionNames")); + Assert.isAssignable(EvictionPolicy.class, evictionPolicyAttributes.annotationType()); + + return from(evictionPolicyAttributes.getEnum("type"), + (Integer) evictionPolicyAttributes.get("maximum"), + evictionPolicyAttributes.getEnum("action"), + resolveObjectSizer(evictionPolicyAttributes.getString("objectSizerName"), applicationContext), + evictionPolicyAttributes.getStringArray("regionNames")); } protected static EvictionPolicyMetaData from(EvictionPolicy evictionPolicy, ApplicationContext applicationContext) { - return from(evictionPolicy.maximum(), evictionPolicy.type(), evictionPolicy.action(), + return from(evictionPolicy.type(), evictionPolicy.maximum(), evictionPolicy.action(), resolveObjectSizer(evictionPolicy.objectSizerName(), applicationContext), evictionPolicy.regionNames()); } - protected static EvictionPolicyMetaData from(int maximum, EvictionPolicyType type, EvictionActionType action, + protected static EvictionPolicyMetaData from(EvictionPolicyType type, int maximum, EvictionActionType action, ObjectSizer objectSizer, String... regionNames) { EvictionAttributesFactoryBean factoryBean = new EvictionAttributesFactoryBean(); @@ -329,6 +341,7 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa } protected static ObjectSizer resolveObjectSizer(String objectSizerName, ApplicationContext applicationContext) { + boolean resolvable = StringUtils.hasText(objectSizerName) && applicationContext.containsBean(objectSizerName); @@ -374,10 +387,11 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa * @see org.apache.geode.cache.EvictionAttributes */ protected EvictionPolicyMetaData(EvictionAttributes evictionAttributes, String[] regionNames) { - Assert.notNull(evictionAttributes, "EvictionAttributes must not be null"); - this.evictionAttributes = evictionAttributes; - Collections.addAll(this.regionNames, ArrayUtils.nullSafeArray(regionNames, String.class)); + this.evictionAttributes = Optional.ofNullable(evictionAttributes) + .orElseThrow(() -> newIllegalArgumentException("EvictionAttributes are required")); + + Collections.addAll(this.regionNames, nullSafeArray(regionNames, String.class)); } /** @@ -390,10 +404,8 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa * @see org.apache.geode.cache.EvictionAttributes */ protected EvictionAttributes getEvictionAttributes() { - Assert.state(this.evictionAttributes != null, - "EvictionAttributes was not properly configured and initialized"); - - return this.evictionAttributes; + return Optional.ofNullable(this.evictionAttributes).orElseThrow(() -> + newIllegalStateException("EvictionAttributes was not properly configured and initialized")); } /** @@ -403,10 +415,10 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa * @return a boolean value indicating whether the {@link Object} is accepted for Eviction policy configuration. * @see #isRegionFactoryBean(Object) * @see #resolveRegionName(Object) - * @see #accepts(String) + * @see #accepts(Supplier) */ protected boolean accepts(Object regionFactoryBean) { - return (isRegionFactoryBean(regionFactoryBean) && accepts(resolveRegionName(regionFactoryBean))); + return (isRegionFactoryBean(regionFactoryBean) && accepts(() -> resolveRegionName(regionFactoryBean))); } /** @@ -415,8 +427,8 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa * @param regionName name of the {@link Region} targeted for Eviction policy configuration. * @return a boolean value if the named {@link Region} is accepted for Eviction policy configuration. */ - protected boolean accepts(String regionName) { - return (this.regionNames.isEmpty() || this.regionNames.contains(regionName)); + protected boolean accepts(Supplier regionName) { + return (this.regionNames.isEmpty() || this.regionNames.contains(regionName.get())); } /** @@ -444,6 +456,7 @@ public class EvictionConfiguration implements ApplicationContextAware, ImportAwa * @see #getEvictionAttributes() */ protected Object setEvictionAttributes(Object regionFactoryBean) { + if (regionFactoryBean instanceof RegionFactoryBean) { ((RegionFactoryBean) regionFactoryBean).setEvictionAttributes(getEvictionAttributes()); } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ExpirationConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ExpirationConfiguration.java index 1de1b538..6fa67f7b 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/ExpirationConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ExpirationConfiguration.java @@ -19,11 +19,15 @@ package org.springframework.data.gemfire.config.annotation; import static org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationPolicy; import static org.springframework.data.gemfire.config.annotation.EnableExpiration.ExpirationType; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import org.apache.geode.cache.AttributesMutator; @@ -39,9 +43,7 @@ import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.expiration.AnnotationBasedExpiration; import org.springframework.data.gemfire.expiration.ExpirationActionType; -import org.springframework.data.gemfire.util.ArrayUtils; import org.springframework.data.gemfire.util.CollectionUtils; -import org.springframework.data.gemfire.util.SpringUtils; import org.springframework.util.Assert; /** @@ -70,7 +72,7 @@ public class ExpirationConfiguration implements ImportAware { /** * Returns the {@link Annotation} {@link Class type} that enables and configures Expiration. * - * @return {@link Annotation} {@link Class type} that enables and configures Expiration. + * @return the {@link Annotation} {@link Class type} that enables and configures Expiration. * @see java.lang.annotation.Annotation * @see java.lang.Class */ @@ -106,22 +108,22 @@ public class ExpirationConfiguration implements ImportAware { */ @Override public void setImportMetadata(AnnotationMetadata importMetadata) { + if (importMetadata.hasAnnotation(getAnnotationTypeName())) { Map enableExpirationAttributes = importMetadata.getAnnotationAttributes(getAnnotationTypeName()); - AnnotationAttributes[] policies = - (AnnotationAttributes[]) enableExpirationAttributes.get("policies"); + AnnotationAttributes[] policies = (AnnotationAttributes[]) enableExpirationAttributes.get("policies"); for (AnnotationAttributes expirationPolicyAttributes : - ArrayUtils.nullSafeArray(policies, AnnotationAttributes.class)) { + nullSafeArray(policies, AnnotationAttributes.class)) { this.expirationPolicyConfigurer = ComposableExpirationPolicyConfigurer.compose( this.expirationPolicyConfigurer, ExpirationPolicyMetaData.from(expirationPolicyAttributes)); } - this.expirationPolicyConfigurer = (this.expirationPolicyConfigurer != null ? this.expirationPolicyConfigurer - : ExpirationPolicyMetaData.fromDefaults()); + this.expirationPolicyConfigurer = Optional.ofNullable(this.expirationPolicyConfigurer) + .orElseGet(ExpirationPolicyMetaData::fromDefaults); } } @@ -137,16 +139,16 @@ public class ExpirationConfiguration implements ImportAware { } protected ExpirationPolicyConfigurer getExpirationPolicyConfigurer() { - Assert.state(this.expirationPolicyConfigurer != null, - "ExpirationPolicyConfigurer was not properly configured and initialized"); - - return expirationPolicyConfigurer; + return Optional.ofNullable(this.expirationPolicyConfigurer).orElseThrow(() -> + newIllegalStateException("ExpirationPolicyConfigurer was not properly configured and initialized")); } @Bean @SuppressWarnings("unused") public BeanPostProcessor expirationBeanPostProcessor() { + return new BeanPostProcessor() { + @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; @@ -158,7 +160,6 @@ public class ExpirationConfiguration implements ImportAware { return (isRegion(bean) ? getExpirationPolicyConfigurer().configure((Region) bean) : bean); } - }; } @@ -170,13 +171,11 @@ public class ExpirationConfiguration implements ImportAware { /** * Configures the expiration policy for the given {@link Region}. * - * @param {@link Class type} of the {@link Region} keys. - * @param {@link Class type} of the {@link Region} values. - * @param region {@link Region} who's expiration policy will be configured. - * @return the given {@link Region}. + * @param region {@link Region} object who's expiration policy will be configured. + * @return the given {@link Region} object. * @see org.apache.geode.cache.Region */ - Region configure(Region region); + Object configure(Object region); } @@ -202,7 +201,7 @@ public class ExpirationConfiguration implements ImportAware { * @see #compose(Iterable) */ protected static ExpirationPolicyConfigurer compose(ExpirationPolicyConfigurer[] array) { - return compose(Arrays.asList(ArrayUtils.nullSafeArray(array, ExpirationPolicyConfigurer.class))); + return compose(Arrays.asList(nullSafeArray(array, ExpirationPolicyConfigurer.class))); } /** @@ -214,9 +213,10 @@ public class ExpirationConfiguration implements ImportAware { * @see #compose(ExpirationPolicyConfigurer, ExpirationPolicyConfigurer) */ protected static ExpirationPolicyConfigurer compose(Iterable iterable) { + ExpirationPolicyConfigurer current = null; - for (ExpirationPolicyConfigurer configurer : CollectionUtils.nullSafeIterable(iterable)) { + for (ExpirationPolicyConfigurer configurer : nullSafeIterable(iterable)) { current = compose(current, configurer); } @@ -255,7 +255,7 @@ public class ExpirationConfiguration implements ImportAware { * @inheritDoc */ @Override - public Region configure(Region region) { + public Object configure(Object region) { return this.two.configure(this.one.configure(region)); } } @@ -276,9 +276,9 @@ public class ExpirationConfiguration implements ImportAware { private final ExpirationAttributes defaultExpirationAttributes; - private final Set regionNames = new HashSet(); + private final Set regionNames = new HashSet<>(); - private final Set types = new HashSet(); + private final Set types = new HashSet<>(); /** * Factory method to construct an instance of {@link ExpirationPolicyMetaData} initialized with @@ -294,12 +294,13 @@ public class ExpirationConfiguration implements ImportAware { * @see org.springframework.core.annotation.AnnotationAttributes */ protected static ExpirationPolicyMetaData from(AnnotationAttributes expirationPolicyAttributes) { + Assert.isAssignable(ExpirationPolicy.class, expirationPolicyAttributes.annotationType()); return newExpirationPolicyMetaData((Integer) expirationPolicyAttributes.get("timeout"), - expirationPolicyAttributes.getEnum("action"), - expirationPolicyAttributes.getStringArray("regionNames"), - (ExpirationType[]) expirationPolicyAttributes.get("types")); + expirationPolicyAttributes.getEnum("action"), + expirationPolicyAttributes.getStringArray("regionNames"), + (ExpirationType[]) expirationPolicyAttributes.get("types")); } /** @@ -378,8 +379,8 @@ public class ExpirationConfiguration implements ImportAware { String[] regionNames, ExpirationType[] types) { return new ExpirationPolicyMetaData(newExpirationAttributes(timeout, action), - CollectionUtils.asSet(ArrayUtils.nullSafeArray(regionNames, String.class)), - CollectionUtils.asSet(ArrayUtils.nullSafeArray(types, ExpirationType.class))); + CollectionUtils.asSet(nullSafeArray(regionNames, String.class)), + CollectionUtils.asSet(nullSafeArray(types, ExpirationType.class))); } /** @@ -391,7 +392,7 @@ public class ExpirationConfiguration implements ImportAware { * @see ExpirationActionType */ protected static ExpirationActionType resolveAction(ExpirationActionType action) { - return SpringUtils.defaultIfNull(action, DEFAULT_ACTION); + return Optional.ofNullable(action).orElse(DEFAULT_ACTION); } /** @@ -421,6 +422,7 @@ public class ExpirationConfiguration implements ImportAware { * @see #resolveAction(ExpirationActionType) * @see #resolveTimeout(int) */ + @SuppressWarnings("unused") protected ExpirationPolicyMetaData(int timeout, ExpirationActionType action, Set regionNames, Set types) { @@ -442,7 +444,7 @@ public class ExpirationConfiguration implements ImportAware { protected ExpirationPolicyMetaData(ExpirationAttributes expirationAttributes, Set regionNames, Set types) { - Assert.notEmpty(types, "At least one ExpirationPolicy type [TTI, TTL] must be specified"); + Assert.notEmpty(types, "At least one ExpirationPolicy type [TTI, TTL] is required"); this.defaultExpirationAttributes = expirationAttributes; this.regionNames.addAll(CollectionUtils.nullSafeSet(regionNames)); @@ -457,8 +459,8 @@ public class ExpirationConfiguration implements ImportAware { * @see org.apache.geode.cache.Region * @see #accepts(String) */ - protected boolean accepts(Region region) { - return (region != null && accepts(region.getName())); + protected boolean accepts(Object region) { + return (region instanceof Region && accepts(((Region) region).getName())); } /** @@ -496,24 +498,27 @@ public class ExpirationConfiguration implements ImportAware { * @inheritDoc */ @Override - public Region configure(Region region) { - if (accepts(region)) { - AttributesMutator regionAttributesMutator = region.getAttributesMutator(); + public Object configure(Object regionObject) { + + if (accepts(regionObject)) { + Region region = (Region) regionObject; + + AttributesMutator regionAttributesMutator = region.getAttributesMutator(); ExpirationAttributes defaultExpirationAttributes = defaultExpirationAttributes(); if (isIdleTimeout()) { regionAttributesMutator.setCustomEntryIdleTimeout( - AnnotationBasedExpiration.forIdleTimeout(defaultExpirationAttributes)); + AnnotationBasedExpiration.forIdleTimeout(defaultExpirationAttributes)); } if (isTimeToLive()) { regionAttributesMutator.setCustomEntryTimeToLive( - AnnotationBasedExpiration.forTimeToLive(defaultExpirationAttributes)); + AnnotationBasedExpiration.forTimeToLive(defaultExpirationAttributes)); } } - return region; + return regionObject; } /** diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfiguration.java index 7202b57f..e66a2aec 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfiguration.java @@ -17,12 +17,20 @@ package org.springframework.data.gemfire.config.annotation; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap; + import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import org.apache.geode.cache.Region; import org.apache.geode.cache.lucene.LuceneIndex; import org.apache.geode.cache.query.Index; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.annotation.AnnotationAttributes; @@ -48,23 +56,29 @@ import org.springframework.util.StringUtils; * * @author John Blum * @see java.lang.annotation.Annotation + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.lucene.LuceneIndex + * @see org.apache.geode.cache.query.Index * @see org.springframework.beans.factory.support.BeanDefinitionBuilder * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see org.springframework.core.type.AnnotationMetadata * @see org.springframework.data.annotation.Id * @see org.springframework.data.gemfire.IndexFactoryBean * @see org.springframework.data.gemfire.IndexType - * @see org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean + * @see org.springframework.data.gemfire.config.annotation.EnableIndexing * @see org.springframework.data.gemfire.config.annotation.EntityDefinedRegionsConfiguration * @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity * @see org.springframework.data.gemfire.mapping.GemfirePersistentProperty * @see org.springframework.data.gemfire.mapping.annotation.Indexed - * @see org.apache.geode.cache.Region - * @see org.apache.geode.cache.lucene.LuceneIndex - * @see org.apache.geode.cache.query.Index + * @see org.springframework.data.gemfire.mapping.annotation.LuceneIndexed + * @see org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean * @since 1.9.0 */ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { + @Autowired(required = false) + private List indexConfigurers = Collections.emptyList(); + /** * Returns the {@link Annotation} {@link Class type} that configures and creates {@link Region} Indexes * from application persistent entity properties. @@ -116,8 +130,8 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { GemfirePersistentEntity localPersistentEntity = super.postProcess(importingClassMetadata, registry, persistentEntity); - if (isAnnotationPresent(importingClassMetadata, getEnableIndexingAnnotationTypeName())) { + AnnotationAttributes enableIndexingAttributes = getAnnotationAttributes(importingClassMetadata, getEnableIndexingAnnotationTypeName()); @@ -169,6 +183,7 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { IndexType indexType, Annotation indexAnnotation, BeanDefinitionRegistry registry) { Optional.ofNullable(indexAnnotation).ifPresent(localIndexAnnotation -> { + AnnotationAttributes indexedAttributes = getAnnotationAttributes(localIndexAnnotation); BeanDefinitionBuilder indexFactoryBeanBuilder = @@ -186,6 +201,8 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { indexFactoryBeanBuilder.addPropertyValue("from", resolveFrom(persistentEntity, persistentProperty, indexedAttributes)); + indexFactoryBeanBuilder.addPropertyValue("indexConfigurers", resolveIndexConfigurers()); + indexFactoryBeanBuilder.addPropertyValue("name", indexName); indexFactoryBeanBuilder.addPropertyValue("override", @@ -222,6 +239,7 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { Annotation luceneIndexAnnotation, BeanDefinitionRegistry registry) { Optional.ofNullable(luceneIndexAnnotation).ifPresent(localLuceneIndexAnnotation -> { + AnnotationAttributes luceneIndexAttributes = AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes(localLuceneIndexAnnotation)); @@ -237,6 +255,8 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { luceneIndexFactoryBeanBuilder.addPropertyValue("fields", persistentProperty.getName()); + luceneIndexFactoryBeanBuilder.addPropertyValue("indexConfigurers", resolveIndexConfigurers()); + luceneIndexFactoryBeanBuilder.addPropertyValue("indexName", indexName); luceneIndexFactoryBeanBuilder.addPropertyValue("regionPath", persistentEntity.getRegionName()); @@ -245,6 +265,24 @@ public class IndexConfiguration extends EntityDefinedRegionsConfiguration { }); } + /* (non-Javadoc) */ + private List resolveIndexConfigurers() { + + return Optional.ofNullable(this.indexConfigurers) + .filter(indexConfigurers -> !indexConfigurers.isEmpty()) + .orElseGet(() -> + Optional.of(getBeanFactory()) + .filter(beanFactory -> beanFactory instanceof ListableBeanFactory) + .map(beanFactory -> { + Map beansOfType = ((ListableBeanFactory) beanFactory) + .getBeansOfType(IndexConfigurer.class, true, true); + + return nullSafeMap(beansOfType).values().stream().collect(Collectors.toList()); + }) + .orElseGet(Collections::emptyList) + ); + } + /* (non-Javadoc) */ private boolean resolveDefine(AnnotationAttributes enableIndexingAttributes) { return (enableIndexingAttributes.containsKey("define") diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfigurer.java b/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfigurer.java new file mode 100644 index 00000000..6252cea0 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/IndexConfigurer.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017 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.config.annotation; + +import org.apache.geode.cache.lucene.LuceneIndex; +import org.apache.geode.cache.query.Index; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.gemfire.IndexFactoryBean; +import org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean; + +/** + * The {@link IndexConfigurer} interface defines a contract for implementations to customize the configuration + * of Entity-defined {@link Index Indexes} when a user annotates her Spring application {@link Configuration} + * class with {@link EnableIndexing}. + * + * @author John Blum + * @see org.apache.geode.cache.lucene.LuceneIndex + * @see org.apache.geode.cache.query.Index + * @see org.springframework.data.gemfire.IndexFactoryBean + * @see org.springframework.data.gemfire.config.annotation.EnableIndexing + * @see org.springframework.data.gemfire.config.annotation.IndexConfiguration + * @see org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean + * @since 1.1.0 + */ +public interface IndexConfigurer { + + /** + * Configuration callback method providing a reference to a {@link IndexFactoryBean} used to construct, configure + * and initialize an instance of a peer {@link Index}. + * + * @param beanName name of {@link Index} bean declared in the Spring application context. + * @param bean reference to the {@link IndexFactoryBean}. + * @see org.springframework.data.gemfire.IndexFactoryBean + */ + default void configure(String beanName, IndexFactoryBean bean) { + } + + /** + * Configuration callback method providing a reference to a {@link LuceneIndexFactoryBean} used to construct, + * configure and initialize an instance of a peer {@link LuceneIndex}. + * + * @param beanName name of {@link LuceneIndex} bean declared in the Spring application context. + * @param bean reference to the {@link LuceneIndexFactoryBean}. + * @see org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean + */ + default void configure(String beanName, LuceneIndexFactoryBean bean) { + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorConfiguration.java index c3b4b2f2..b32224a0 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/LocatorConfiguration.java @@ -50,7 +50,7 @@ public class LocatorConfiguration extends EmbeddedServiceConfigurationSupport { String host = resolveHost((String) annotationAttributes.get("host")); int port = resolvePort((Integer) annotationAttributes.get("port"), DEFAULT_LOCATOR_PORT); - return new PropertiesBuilder() + return PropertiesBuilder.create() .setProperty(START_LOCATOR_GEMFIRE_SYSTEM_PROPERTY_NAME, String.format("%s[%d]", host, port)) .build(); } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/ManagerConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/ManagerConfiguration.java index 32909c92..dc84e588 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/ManagerConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/ManagerConfiguration.java @@ -44,7 +44,7 @@ public class ManagerConfiguration extends EmbeddedServiceConfigurationSupport { @Override protected Properties toGemFireProperties(Map annotationAttributes) { - PropertiesBuilder gemfireProperties = new PropertiesBuilder(); + PropertiesBuilder gemfireProperties = PropertiesBuilder.create(); gemfireProperties.setProperty("jmx-manager", Boolean.TRUE.toString()); gemfireProperties.setProperty("jmx-manager-access-file", annotationAttributes.get("accessFile")); diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/MemcachedServerConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/MemcachedServerConfiguration.java index c92faded..e8b1723c 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/MemcachedServerConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/MemcachedServerConfiguration.java @@ -44,8 +44,9 @@ public class MemcachedServerConfiguration extends EmbeddedServiceConfigurationSu @Override protected Properties toGemFireProperties(Map annotationAttributes) { - return new PropertiesBuilder() - .setProperty("memcached-port", resolvePort((Integer) annotationAttributes.get("port"), DEFAULT_MEMCACHED_SERVER_PORT)) + return PropertiesBuilder.create() + .setProperty("memcached-port", resolvePort((Integer) annotationAttributes.get("port"), + DEFAULT_MEMCACHED_SERVER_PORT)) .setProperty("memcached-protocol", annotationAttributes.get("protocol")) .build(); } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/PeerCacheConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/PeerCacheConfiguration.java index 5e8ffc85..e1e534c1 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/PeerCacheConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/PeerCacheConfiguration.java @@ -17,22 +17,31 @@ package org.springframework.data.gemfire.config.annotation; -import java.util.Map; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.geode.cache.Cache; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.CacheFactoryBean; /** - * Spring {@link Configuration} class used to configure, construct and initialize - * a GemFire peer {@link org.apache.geode.cache.Cache} instance in a Spring application context. + * Spring {@link Configuration} class used to construct, configure and initialize a peer {@link Cache} instance + * in a Spring application context. * * @author John Blum + * @see org.apache.geode.cache.Cache * @see org.springframework.context.annotation.Bean * @see org.springframework.context.annotation.Configuration * @see org.springframework.data.gemfire.config.annotation.AbstractCacheConfiguration - * @see org.apache.geode.cache.Cache * @since 1.9.0 */ @Configuration @@ -52,14 +61,28 @@ public class PeerCacheConfiguration extends AbstractCacheConfiguration { private Integer messageSyncInterval; private Integer searchTimeout; + @Autowired(required = false) + private List peerCacheConfigurers = Collections.emptyList(); + + /** + * Bean declaration for a single, peer {@link Cache} instance. + * + * @return a new instance of a peer {@link Cache}. + * @see org.springframework.data.gemfire.CacheFactoryBean + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Cache + * @see #constructCacheFactoryBean() + */ @Bean public CacheFactoryBean gemfireCache() { + CacheFactoryBean gemfireCache = constructCacheFactoryBean(); gemfireCache.setEnableAutoReconnect(enableAutoReconnect()); gemfireCache.setLockLease(lockLease()); gemfireCache.setLockTimeout(lockTimeout()); gemfireCache.setMessageSyncInterval(messageSyncInterval()); + gemfireCache.setPeerCacheConfigurers(resolvePeerCacheConfigurers()); gemfireCache.setSearchTimeout(searchTimeout()); gemfireCache.setUseBeanFactoryLocator(useBeanFactoryLocator()); gemfireCache.setUseClusterConfiguration(useClusterConfiguration()); @@ -67,8 +90,30 @@ public class PeerCacheConfiguration extends AbstractCacheConfiguration { return gemfireCache; } + /* (non-Javadoc) */ + private List resolvePeerCacheConfigurers() { + + return Optional.ofNullable(this.peerCacheConfigurers) + .filter(peerCacheConfigurers -> !peerCacheConfigurers.isEmpty()) + .orElseGet(() -> + Optional.of(this.beanFactory()) + .filter(beanFactory -> beanFactory instanceof ListableBeanFactory) + .map(beanFactory -> { + Map beansOfType = ((ListableBeanFactory) beanFactory) + .getBeansOfType(PeerCacheConfigurer.class, true, true); + + return nullSafeMap(beansOfType).values().stream().collect(Collectors.toList()); + }) + .orElseGet(Collections::emptyList) + ); + } + /** - * {@inheritDoc} + * Constructs a new instance of {@link CacheFactoryBean} used to create a peer {@link Cache}. + * + * @param {@link Class} sub-type of {@link CacheFactoryBean}. + * @return a new instance of {@link CacheFactoryBean}. + * @see org.springframework.data.gemfire.CacheFactoryBean */ @Override @SuppressWarnings("unchecked") @@ -77,18 +122,20 @@ public class PeerCacheConfiguration extends AbstractCacheConfiguration { } /** - * Configures GemFire peer {@link org.apache.geode.cache.Cache} specific settings. + * Configures peer {@link Cache} specific settings. * - * @param importMetadata {@link AnnotationMetadata} containing peer cache meta-data used to configure - * the GemFire peer {@link org.apache.geode.cache.Cache}. + * @param importMetadata {@link AnnotationMetadata} containing peer cache meta-data used to + * configure the peer {@link Cache}. * @see org.springframework.core.type.AnnotationMetadata * @see #isCacheServerOrPeerCacheApplication(AnnotationMetadata) */ @Override protected void configureCache(AnnotationMetadata importMetadata) { + super.configureCache(importMetadata); if (isCacheServerOrPeerCacheApplication(importMetadata)) { + Map peerCacheApplicationAttributes = importMetadata.getAnnotationAttributes(getAnnotationTypeName()); @@ -99,11 +146,9 @@ public class PeerCacheConfiguration extends AbstractCacheConfiguration { setSearchTimeout((Integer) peerCacheApplicationAttributes.get("searchTimeout")); setUseClusterConfiguration(Boolean.TRUE.equals(peerCacheApplicationAttributes.get("useClusterConfiguration"))); - String locators = (String) peerCacheApplicationAttributes.get("locators"); - - if (hasValue(locators)) { - setLocators(locators); - } + Optional.ofNullable((String) peerCacheApplicationAttributes.get("locators")) + .filter(PeerCacheConfiguration::hasValue) + .ifPresent(this::setLocators); } } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/PeerCacheConfigurer.java b/src/main/java/org/springframework/data/gemfire/config/annotation/PeerCacheConfigurer.java new file mode 100644 index 00000000..a2b4b8fb --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/PeerCacheConfigurer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016 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.config.annotation; + +import org.apache.geode.cache.Cache; +import org.springframework.data.gemfire.CacheFactoryBean; + +/** + * The {@link PeerCacheConfigurer} interface defines a contract for implementations to customize the configuration + * of a {@link CacheFactoryBean} used to construct, configure and initialize an instance of a peer {@link Cache}. + * + * @author John Blum + * @see org.apache.geode.cache.Cache + * @see org.springframework.data.gemfire.CacheFactoryBean + * @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication + * @since 1.9.0 + */ +public interface PeerCacheConfigurer { + + /** + * Configuration callback method providing a reference to a {@link CacheFactoryBean} used to construct, + * configure and initialize an instance of a peer {@link Cache}. + * + * @param beanName name of peer {@link Cache} bean declared in the Spring application context. + * @param bean reference to the {@link CacheFactoryBean}. + * @see org.springframework.data.gemfire.CacheFactoryBean + */ + void configure(String beanName, CacheFactoryBean bean); + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/PoolConfigurer.java b/src/main/java/org/springframework/data/gemfire/config/annotation/PoolConfigurer.java new file mode 100644 index 00000000..474ad6d4 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/PoolConfigurer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017 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.config.annotation; + +import org.apache.geode.cache.client.Pool; +import org.springframework.data.gemfire.client.PoolFactoryBean; + +/** + * The {@link PoolConfigurer} interface defines a contract for implementations to customize the configuration + * of a {@link PoolFactoryBean} used to construct, configure and initialize a {@link Pool}. + * + * @author John Blum + * @see org.apache.geode.cache.client.Pool + * @see org.springframework.data.gemfire.client.PoolFactoryBean + * @see org.springframework.data.gemfire.config.annotation.AddPoolConfiguration + * @see org.springframework.data.gemfire.config.annotation.AddPoolsConfiguration + * @see org.springframework.data.gemfire.config.annotation.EnablePool + * @see org.springframework.data.gemfire.config.annotation.EnablePools + * @since 1.1.0 + */ +public interface PoolConfigurer { + + /** + * Configuration callback method providing a reference to a {@link PoolFactoryBean} used to construct, + * configure and initialize an instance of a {@link Pool}. + * + * @param beanName name of the {@link Pool} bean declared in the Spring application context. + * @param bean reference to the {@link PoolFactoryBean}. + * @see org.springframework.data.gemfire.client.PoolFactoryBean + */ + void configure(String beanName, PoolFactoryBean bean); + +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/RedisServerConfiguration.java b/src/main/java/org/springframework/data/gemfire/config/annotation/RedisServerConfiguration.java index 8d8cbe42..2ab1f9c4 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/RedisServerConfiguration.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/RedisServerConfiguration.java @@ -44,7 +44,7 @@ public class RedisServerConfiguration extends EmbeddedServiceConfigurationSuppor @Override protected Properties toGemFireProperties(Map annotationAttributes) { - return new PropertiesBuilder() + return PropertiesBuilder.create() .setProperty("redis-bind-address", annotationAttributes.get("bindAddress")) .setProperty("redis-port", resolvePort((Integer)annotationAttributes.get("port"), DEFAULT_REDIS_PORT)) .build(); diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/RegionConfigurer.java b/src/main/java/org/springframework/data/gemfire/config/annotation/RegionConfigurer.java new file mode 100644 index 00000000..fec908d5 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/RegionConfigurer.java @@ -0,0 +1,60 @@ +/* + * Copyright 2017 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.config.annotation; + +import org.apache.geode.cache.Region; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.gemfire.RegionFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; + +/** + * The {@link RegionConfigurer} interface defines a contract for implementations to customize the configuration + * of Entity-defined {@link Region Regions} when a user annotates her Spring application {@link Configuration} + * class with {@link EnableEntityDefinedRegions}. + * + * @author John Blum + * @see org.apache.geode.cache.Region + * @see org.springframework.data.gemfire.RegionFactoryBean + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean + * @see org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions + * @see org.springframework.data.gemfire.config.annotation.support.GemFireCacheTypeAwareRegionFactoryBean + * @since 1.1.0 + */ +public interface RegionConfigurer { + + /** + * Configuration callback method providing a reference to a {@link RegionFactoryBean} used to construct, configure + * and initialize an instance of a peer {@link Region}. + * + * @param beanName name of {@link Region} bean declared in the Spring application context. + * @param bean reference to the {@link RegionFactoryBean}. + * @see org.springframework.data.gemfire.RegionFactoryBean + */ + default void configure(String beanName, RegionFactoryBean bean) { + } + + /** + * Configuration callback method providing a reference to a {@link ClientRegionFactoryBean} used to construct, + * configure and initialize an instance of a client {@link Region}. + * + * @param beanName name of {@link Region} bean declared in the Spring application context. + * @param bean reference to the {@link ClientRegionFactoryBean}. + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean + */ + default void configure(String beanName, ClientRegionFactoryBean bean) { + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupport.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupport.java new file mode 100644 index 00000000..7cff11c2 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/AbstractAnnotationConfigSupport.java @@ -0,0 +1,249 @@ +/* + * Copyright 2016 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.config.annotation.support; + +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; + +import java.util.Optional; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.expression.BeanFactoryAccessor; +import org.springframework.context.expression.EnvironmentAccessor; +import org.springframework.context.expression.MapAccessor; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.expression.spel.support.StandardTypeConverter; +import org.springframework.expression.spel.support.StandardTypeLocator; +import org.springframework.util.StringUtils; + +/** + * The {@link AbstractAnnotationConfigSupport} class is an abstract base class encapsulating functionality + * common to all Annotations and configuration classes used to configure Pivotal GemFire/Apache Geode objects + * with Spring Data GemFire or Spring Data Geode. + * + * @author John Blum + * @see java.lang.ClassLoader + * @see org.springframework.beans.factory.BeanClassLoaderAware + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.InitializingBean + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory + * @see org.springframework.beans.factory.support.AbstractBeanDefinition + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see org.springframework.expression.EvaluationContext + * @since 1.9.0 + */ +@SuppressWarnings("unused") +public abstract class AbstractAnnotationConfigSupport + implements BeanClassLoaderAware, BeanFactoryAware, InitializingBean { + + private BeanFactory beanFactory; + + private ClassLoader beanClassLoader; + + private EvaluationContext evaluationContext; + + /** + * Determines whether the given {@link Object} has value. The {@link Object} is valuable + * if it is not {@literal null}. + * + * @param value {@link Object} to evaluate. + * @return a boolean value indicating whether the given {@link Object} has value. + */ + protected static boolean hasValue(Object value) { + return Optional.ofNullable(value).isPresent(); + } + + /** + * Determines whether the given {@link Number} has value. The {@link Number} is valuable + * if it is not {@literal null} and is not equal to 0.0d. + * + * @param value {@link Number} to evaluate. + * @return a boolean value indicating whether the given {@link Number} has value. + */ + protected static boolean hasValue(Number value) { + return Optional.ofNullable(value).filter(it -> it.doubleValue() != 0.0d).isPresent(); + } + + /** + * Determines whether the given {@link String} has value. The {@link String} is valuable + * if it is not {@literal null} or empty. + * + * @param value {@link String} to evaluate. + * @return a boolean value indicating whether the given {@link String} is valuable. + */ + protected static boolean hasValue(String value) { + return StringUtils.hasText(value); + } + + @Override + public void afterPropertiesSet() throws Exception { + this.evaluationContext = newEvaluationContext(); + } + + /** + * Constructs, configures and initializes a new instance of an {@link EvaluationContext}. + * + * @return a new {@link EvaluationContext}. + * @see org.springframework.expression.EvaluationContext + * @see #beanFactory() + */ + protected EvaluationContext newEvaluationContext() { + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); + + evaluationContext.addPropertyAccessor(new BeanFactoryAccessor()); + evaluationContext.addPropertyAccessor(new EnvironmentAccessor()); + evaluationContext.addPropertyAccessor(new MapAccessor()); + evaluationContext.setTypeLocator(new StandardTypeLocator(beanClassLoader())); + + Optional.ofNullable(beanFactory()) + .filter(beanFactory -> beanFactory instanceof ConfigurableBeanFactory) + .map(beanFactory -> ((ConfigurableBeanFactory) beanFactory).getConversionService()) + .ifPresent(conversionService -> + evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService))); + + return evaluationContext; + } + + /** + * Returns the cache application {@link java.lang.annotation.Annotation} type pertaining to this configuration. + * + * @return the cache application {@link java.lang.annotation.Annotation} type used by this application. + */ + protected abstract Class getAnnotationType(); + + /** + * Returns the fully-qualified {@link Class#getName() class name} of the cache application + * {@link java.lang.annotation.Annotation} type. + * + * @return the fully-qualified {@link Class#getName() class name} of the cache application + * {@link java.lang.annotation.Annotation} type. + * @see java.lang.Class#getName() + * @see #getAnnotationType() + */ + protected String getAnnotationTypeName() { + return getAnnotationType().getName(); + } + + /** + * Returns the simple {@link Class#getName() class name} of the cache application + * {@link java.lang.annotation.Annotation} type. + * + * @return the simple {@link Class#getName() class name} of the cache application + * {@link java.lang.annotation.Annotation} type. + * @see java.lang.Class#getSimpleName() + * @see #getAnnotationType() + */ + protected String getAnnotationTypeSimpleName() { + return getAnnotationType().getSimpleName(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + /** + * Returns a reference to the {@link ClassLoader} use by the Spring {@link BeanFactory} to load classes + * for bean definitions. + * + * @return the {@link ClassLoader} used by the Spring {@link BeanFactory} to load classes for bean definitions. + * @see #setBeanClassLoader(ClassLoader) + */ + protected ClassLoader beanClassLoader() { + return this.beanClassLoader; + } + + /** + * {@inheritDoc} + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + /** + * Returns a reference to the Spring {@link BeanFactory} in the current application context. + * + * @return a reference to the Spring {@link BeanFactory}. + * @throws IllegalStateException if the Spring {@link BeanFactory} was not properly configured. + * @see org.springframework.beans.factory.BeanFactory + */ + protected BeanFactory beanFactory() { + return Optional.ofNullable(this.beanFactory) + .orElseThrow(() -> newIllegalStateException("BeanFactory is required")); + } + + /** + * + * @return + */ + protected EvaluationContext evaluationContext() { + return this.evaluationContext; + } + + /** + * Registers the {@link AbstractBeanDefinition} with the {@link BeanDefinitionRegistry} using a generated bean name. + * + * @param beanDefinition {@link AbstractBeanDefinition} to register. + * @return the given {@link AbstractBeanDefinition}. + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.support.AbstractBeanDefinition + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition, BeanDefinitionRegistry) + * @see #beanFactory() + */ + protected AbstractBeanDefinition register(AbstractBeanDefinition beanDefinition) { + + BeanFactory beanFactory = beanFactory(); + + return (beanFactory instanceof BeanDefinitionRegistry + ? register(beanDefinition, (BeanDefinitionRegistry) beanFactory) + : beanDefinition); + } + + /** + * Registers the {@link AbstractBeanDefinition} with the {@link BeanDefinitionRegistry} using a generated bean name. + * + * @param beanDefinition {@link AbstractBeanDefinition} to register. + * @param registry {@link BeanDefinitionRegistry} used to register the {@link AbstractBeanDefinition}. + * @return the given {@link AbstractBeanDefinition}. + * @see org.springframework.beans.factory.support.AbstractBeanDefinition + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry + * @see org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition, BeanDefinitionRegistry) + */ + protected AbstractBeanDefinition register(AbstractBeanDefinition beanDefinition, BeanDefinitionRegistry registry) { + + Optional.ofNullable(registry).ifPresent(it -> + BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, it) + ); + + return beanDefinition; + } +} diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java index f8081b92..5edb7d42 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/EmbeddedServiceConfigurationSupport.java @@ -17,12 +17,14 @@ package org.springframework.data.gemfire.config.annotation.support; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; + import java.util.Map; +import java.util.Optional; import java.util.Properties; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanDefinitionHolder; @@ -39,18 +41,25 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * The {@link EmbeddedServiceConfigurationSupport} class is an abstract base class supporting the configuration - * of Pivotal GemFire and Apache Geode embedded services. + * The {@link EmbeddedServiceConfigurationSupport} class is an abstract base class supporting + * the configuration of Pivotal GemFire and Apache Geode embedded services. * * @author John Blum + * @see java.util.Properties * @see org.springframework.beans.factory.BeanFactory - * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory + * @see org.springframework.beans.factory.config.BeanDefinitionHolder + * @see org.springframework.beans.factory.support.BeanDefinitionBuilder + * @see org.springframework.beans.factory.support.BeanDefinitionRegistry * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar + * @see org.springframework.core.type.AnnotationMetadata * @see org.springframework.data.gemfire.config.annotation.AbstractCacheConfiguration + * @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport * @since 1.9.0 */ @SuppressWarnings("unused") -public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanDefinitionRegistrar, BeanFactoryAware { +public abstract class EmbeddedServiceConfigurationSupport extends AbstractAnnotationConfigSupport + implements ImportBeanDefinitionRegistrar { public static final Integer DEFAULT_PORT = 0; public static final String DEFAULT_HOST = "localhost"; @@ -59,8 +68,6 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD @SuppressWarnings("all") private AbstractCacheConfiguration cacheConfiguration; - private BeanFactory beanFactory; - /** * Returns a reference to an instance of the {@link AbstractCacheConfiguration} class used to configure * a GemFire (Singleton, client or peer) cache instance along with it's associated, embedded services. @@ -72,59 +79,8 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD */ @SuppressWarnings("unchecked") protected T cacheConfiguration() { - Assert.state(cacheConfiguration != null, "AbstractCacheConfiguration was not properly initialized"); - return (T) this.cacheConfiguration; - } - - /** - * Returns the configured GemFire cache application annotation type - * (e.g. {@link org.springframework.data.gemfire.config.annotation.ClientCacheApplication} - * or {@link org.springframework.data.gemfire.config.annotation.PeerCacheApplication}. - * - * @return an {@link Class annotation} defining the GemFire cache application type. - */ - protected abstract Class getAnnotationType(); - - /** - * Returns the fully-qualified class name of the GemFire cache application annotation type. - * - * @return a fully-qualified class name of the GemFire cache application annotation type. - * @see java.lang.Class#getName() - * @see #getAnnotationType() - */ - protected String getAnnotationTypeName() { - return getAnnotationType().getName(); - } - - /** - * Returns the simple class name of the GemFire cache application annotation type. - * - * @return the simple class name of the GemFire cache application annotation type. - * @see java.lang.Class#getSimpleName() - * @see #getAnnotationType() - */ - protected String getAnnotationTypeSimpleName() { - return getAnnotationType().getSimpleName(); - } - - /** - * @inheritDoc - */ - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - /** - * Returns a reference to the Spring {@link BeanFactory}. - * - * @return a reference to the Spring {@link BeanFactory}. - * @throws IllegalStateException if the Spring {@link BeanFactory} was not properly initialized. - * @see org.springframework.beans.factory.BeanFactory - */ - protected BeanFactory getBeanFactory() { - Assert.state(this.beanFactory != null, "BeanFactory was not properly initialized"); - return this.beanFactory; + return Optional.ofNullable((T) this.cacheConfiguration) + .orElseThrow(() -> newIllegalStateException("AbstractCacheConfiguration is required")); } /** @@ -132,7 +88,7 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD */ @Override public final void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, - BeanDefinitionRegistry registry) { + BeanDefinitionRegistry registry) { if (isAnnotationPresent(importingClassMetadata)) { Map annotationAttributes = getAnnotationAttributes(importingClassMetadata); @@ -144,12 +100,12 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD /* (non-Javadoc) */ @SuppressWarnings("unused") protected void registerBeanDefinitions(AnnotationMetadata importingClassMetaData, - Map annotationAttributes, BeanDefinitionRegistry registry) { + Map annotationAttributes, BeanDefinitionRegistry registry) { } /* (non-Javadoc) */ protected void setGemFireProperties(AnnotationMetadata importingClassMetadata, - Map annotationAttributes, BeanDefinitionRegistry registry) { + Map annotationAttributes, BeanDefinitionRegistry registry) { Properties gemfireProperties = toGemFireProperties(annotationAttributes); @@ -183,10 +139,10 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD /* (non-Javadoc) */ protected void registerGemFirePropertiesBeanPostProcessor(BeanDefinitionRegistry registry, - Properties customGemFireProperties) { + Properties customGemFireProperties) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( - GemFirePropertiesBeanPostProcessor.class); + BeanDefinitionBuilder builder = + BeanDefinitionBuilder.genericBeanDefinition(GemFirePropertiesBeanPostProcessor.class); builder.addConstructorArgValue(customGemFireProperties); @@ -200,7 +156,7 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD /* (non-Javadoc) */ protected String generateBeanName() { - return generateBeanName(getAnnotationTypeSimpleName()); + return generateBeanName(getAnnotationType()); } /* (non-Javadoc) */ @@ -228,11 +184,12 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD * @return a Spring managed bean instance for the given, required {@link Class} type, or {@literal null} * if no bean instance of the given, required {@link Class} type could be found. * @throws BeansException if the Spring manage bean of the required {@link Class} type could not be resolved. - * @see #getBeanFactory() + * @see #beanFactory() */ @SuppressWarnings("unchecked") protected T resolveBean(Class beanType) { - BeanFactory beanFactory = getBeanFactory(); + + BeanFactory beanFactory = beanFactory(); if (beanFactory instanceof AutowireCapableBeanFactory) { AutowireCapableBeanFactory autowiringBeanFactory = (AutowireCapableBeanFactory) beanFactory; @@ -252,7 +209,7 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD /* (non-Javadoc) */ protected String resolveHost(String hostname, String defaultHostname) { - return (StringUtils.hasText(hostname) ? hostname : defaultHostname); + return Optional.ofNullable(hostname).filter(StringUtils::hasText).orElse(defaultHostname); } /* (non-Javadoc) */ @@ -262,12 +219,12 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD /* (non-Javadoc) */ protected Integer resolvePort(Integer port, Integer defaultPort) { - return (port != null ? port : defaultPort); + return Optional.ofNullable(port).orElse(defaultPort); } /** - * Spring {@link BeanPostProcessor} used to process GemFire System properties defined as a Spring bean - * in the Spring application context before initialization. + * Spring {@link BeanPostProcessor} used to process before initialization Pivotal GemFire or Apache Geode + * {@link Properties} defined as a bean in the Spring application context. * * @see org.springframework.beans.factory.config.BeanPostProcessor */ @@ -278,15 +235,15 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD private final Properties gemfireProperties; /** - * Construct an instance of the {@link GemFirePropertiesBeanPostProcessor} initialized with - * the given GemFire {@link Properties}. + * Constructs a new instance of the {@link GemFirePropertiesBeanPostProcessor} initialized with + * the given GemFire/Geode {@link Properties}. * - * @param gemfireProperties {@link Properties} used to configure GemFire. - * @throws IllegalArgumentException if the {@link Properties} are null or empty. + * @param gemfireProperties {@link Properties} used to configure Pivotal GemFire or Apache Geode. + * @throws IllegalArgumentException if {@link Properties} are {@literal null} or empty. * @see java.util.Properties */ protected GemFirePropertiesBeanPostProcessor(Properties gemfireProperties) { - Assert.notEmpty(gemfireProperties, "GemFire Properties must not be null or empty"); + Assert.notEmpty(gemfireProperties, "GemFire Properties are required"); this.gemfireProperties = gemfireProperties; } @@ -295,6 +252,7 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD */ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof Properties && GEMFIRE_PROPERTIES_BEAN_NAME.equals(beanName)) { Properties gemfirePropertiesBean = (Properties) bean; gemfirePropertiesBean.putAll(gemfireProperties); @@ -302,13 +260,5 @@ public abstract class EmbeddedServiceConfigurationSupport implements ImportBeanD return bean; } - - /** - * {@inheritDoc} - */ - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - return bean; - } } } diff --git a/src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireCacheTypeAwareRegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireCacheTypeAwareRegionFactoryBean.java index 1e0ee6a7..cbc4bd9d 100644 --- a/src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireCacheTypeAwareRegionFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/config/annotation/support/GemFireCacheTypeAwareRegionFactoryBean.java @@ -17,8 +17,12 @@ package org.springframework.data.gemfire.config.annotation.support; -import static org.springframework.data.gemfire.util.SpringUtils.defaultIfEmpty; -import static org.springframework.data.gemfire.util.SpringUtils.defaultIfNull; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; import org.apache.geode.cache.DataPolicy; import org.apache.geode.cache.GemFireCache; @@ -26,16 +30,14 @@ import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionAttributes; import org.apache.geode.cache.RegionShortcut; import org.apache.geode.cache.client.ClientRegionShortcut; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.FactoryBean; import org.springframework.data.gemfire.GemfireUtils; import org.springframework.data.gemfire.GenericRegionFactoryBean; import org.springframework.data.gemfire.RegionLookupFactoryBean; import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.config.annotation.RegionConfigurer; import org.springframework.data.gemfire.config.xml.GemfireConstants; -import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * The {@link GemFireCacheTypeAwareRegionFactoryBean} class is a smart Spring {@link FactoryBean} that knows how to @@ -43,22 +45,21 @@ import org.springframework.util.Assert; * a {@link org.apache.geode.cache.client.ClientCache} or a peer {@link org.apache.geode.cache.Cache}. * * @author John Blum - * @see org.springframework.beans.factory.BeanFactory - * @see org.springframework.beans.factory.BeanFactoryAware - * @see org.springframework.beans.factory.FactoryBean - * @see org.springframework.data.gemfire.RegionLookupFactoryBean * @see org.apache.geode.cache.GemFireCache * @see org.apache.geode.cache.Region + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.data.gemfire.GenericRegionFactoryBean + * @see org.springframework.data.gemfire.RegionLookupFactoryBean + * @see org.springframework.data.gemfire.RegionFactoryBean + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer * @since 1.9.0 */ @SuppressWarnings("unused") -public class GemFireCacheTypeAwareRegionFactoryBean extends RegionLookupFactoryBean - implements BeanFactoryAware { +public class GemFireCacheTypeAwareRegionFactoryBean extends RegionLookupFactoryBean { private GemFireCache gemfireCache; - private BeanFactory beanFactory; - private Boolean close = false; private Class keyConstraint; @@ -68,6 +69,8 @@ public class GemFireCacheTypeAwareRegionFactoryBean extends RegionLookupFa private DataPolicy dataPolicy = DataPolicy.DEFAULT; + private List regionConfigurers = Collections.emptyList(); + private RegionAttributes regionAttributes; private RegionShortcut serverRegionShortcut; @@ -79,7 +82,8 @@ public class GemFireCacheTypeAwareRegionFactoryBean extends RegionLookupFa * @inheritDoc */ @Override - public Region lookupRegion(GemFireCache gemfireCache, String regionName) throws Exception { + public Region createRegion(GemFireCache gemfireCache, String regionName) throws Exception { + return (GemfireUtils.isClient(gemfireCache) ? newClientRegion(gemfireCache, regionName) : newServerRegion(gemfireCache, regionName)); } @@ -97,20 +101,23 @@ public class GemFireCacheTypeAwareRegionFactoryBean extends RegionLookupFa * @see org.apache.geode.cache.Region */ protected Region newClientRegion(GemFireCache gemfireCache, String regionName) throws Exception { - ClientRegionFactoryBean clientRegion = new ClientRegionFactoryBean<>(); - clientRegion.setAttributes(getRegionAttributes()); - clientRegion.setBeanFactory(getBeanFactory()); - clientRegion.setCache(gemfireCache); - clientRegion.setClose(isClose()); - clientRegion.setKeyConstraint(getKeyConstraint()); - clientRegion.setPoolName(getPoolName()); - clientRegion.setRegionName(regionName); - clientRegion.setShortcut(getClientRegionShortcut()); - clientRegion.setValueConstraint(getValueConstraint()); - clientRegion.afterPropertiesSet(); + ClientRegionFactoryBean clientRegionFactory = new ClientRegionFactoryBean<>(); - return clientRegion.getObject(); + clientRegionFactory.setAttributes(getRegionAttributes()); + clientRegionFactory.setBeanFactory(getBeanFactory()); + clientRegionFactory.setCache(gemfireCache); + clientRegionFactory.setClose(isClose()); + clientRegionFactory.setKeyConstraint(getKeyConstraint()); + clientRegionFactory.setPoolName(getPoolName()); + clientRegionFactory.setRegionConfigurers(this.regionConfigurers); + clientRegionFactory.setRegionName(regionName); + clientRegionFactory.setShortcut(getClientRegionShortcut()); + clientRegionFactory.setValueConstraint(getValueConstraint()); + + clientRegionFactory.afterPropertiesSet(); + + return clientRegionFactory.getObject(); } /** @@ -126,19 +133,22 @@ public class GemFireCacheTypeAwareRegionFactoryBean extends RegionLookupFa * @see org.apache.geode.cache.Region */ protected Region newServerRegion(GemFireCache gemfireCache, String regionName) throws Exception { - GenericRegionFactoryBean serverRegion = new GenericRegionFactoryBean<>(); - serverRegion.setAttributes(getRegionAttributes()); - serverRegion.setCache(gemfireCache); - serverRegion.setClose(isClose()); - serverRegion.setDataPolicy(getDataPolicy()); - serverRegion.setKeyConstraint(getKeyConstraint()); - serverRegion.setRegionName(regionName); - serverRegion.setShortcut(getServerRegionShortcut()); - serverRegion.setValueConstraint(getValueConstraint()); - serverRegion.afterPropertiesSet(); + GenericRegionFactoryBean serverRegionFactory = new GenericRegionFactoryBean<>(); - return serverRegion.getObject(); + serverRegionFactory.setAttributes(getRegionAttributes()); + serverRegionFactory.setCache(gemfireCache); + serverRegionFactory.setClose(isClose()); + serverRegionFactory.setDataPolicy(getDataPolicy()); + serverRegionFactory.setKeyConstraint(getKeyConstraint()); + serverRegionFactory.setRegionConfigurers(this.regionConfigurers); + serverRegionFactory.setRegionName(regionName); + serverRegionFactory.setShortcut(getServerRegionShortcut()); + serverRegionFactory.setValueConstraint(getValueConstraint()); + + serverRegionFactory.afterPropertiesSet(); + + return serverRegionFactory.getObject(); } public void setAttributes(RegionAttributes regionAttributes) { @@ -149,25 +159,12 @@ public class GemFireCacheTypeAwareRegionFactoryBean extends RegionLookupFa return this.regionAttributes; } - /** - * @inheritDoc - */ - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - protected BeanFactory getBeanFactory() { - Assert.state(this.beanFactory != null, "BeanFactory was not properly initialized"); - return this.beanFactory; - } - public void setClientRegionShortcut(ClientRegionShortcut clientRegionShortcut) { this.clientRegionShortcut = clientRegionShortcut; } protected ClientRegionShortcut getClientRegionShortcut() { - return defaultIfNull(this.clientRegionShortcut, ClientRegionShortcut.PROXY); + return Optional.ofNullable(this.clientRegionShortcut).orElse(ClientRegionShortcut.PROXY); } public void setClose(Boolean close) { @@ -187,7 +184,7 @@ public class GemFireCacheTypeAwareRegionFactoryBean extends RegionLookupFa } protected DataPolicy getDataPolicy() { - return defaultIfNull(this.dataPolicy, DataPolicy.DEFAULT); + return Optional.ofNullable(this.dataPolicy).orElse(DataPolicy.DEFAULT); } public void setKeyConstraint(Class keyConstraint) { @@ -203,7 +200,33 @@ public class GemFireCacheTypeAwareRegionFactoryBean extends RegionLookupFa } protected String getPoolName() { - return defaultIfEmpty(this.poolName, GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME); + return Optional.ofNullable(this.poolName).filter(StringUtils::hasText) + .orElse(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME); + } + + /** + * Null-safe operation used to set an array of {@link RegionConfigurer RegionConfigurers} used to apply + * additional configuration to this {@link RegionLookupFactoryBean} when using Annotation-based configuration. + * + * @param regionConfigurers array of {@link RegionConfigurer RegionConfigurers} used to apply + * additional configuration to this {@link RegionLookupFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + * @see #setRegionConfigurers(List) + */ + public void setRegionConfigurers(RegionConfigurer... regionConfigurers) { + setRegionConfigurers(Arrays.asList(nullSafeArray(regionConfigurers, RegionConfigurer.class))); + } + + /** + * Null-safe operation used to set an {@link Iterable} of {@link RegionConfigurer RegionConfigurers} used to apply + * additional configuration to this {@link RegionLookupFactoryBean} when using Annotation-based configuration. + * + * @param regionConfigurers {@link Iterable} of {@link RegionConfigurer RegionConfigurers} used to apply + * additional configuration to this {@link RegionLookupFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + */ + public void setRegionConfigurers(List regionConfigurers) { + this.regionConfigurers = Optional.ofNullable(regionConfigurers).orElseGet(Collections::emptyList); } public void setServerRegionShortcut(RegionShortcut shortcut) { diff --git a/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBean.java b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBean.java index 9080d833..50736415 100644 --- a/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBean.java @@ -17,7 +17,10 @@ package org.springframework.data.gemfire.search.lucene; +import static java.util.stream.StreamSupport.stream; import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeList; import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeMap; import static org.springframework.util.CollectionUtils.isEmpty; @@ -27,6 +30,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Supplier; import org.apache.geode.cache.GemFireCache; import org.apache.geode.cache.Region; @@ -36,26 +40,20 @@ import org.apache.geode.cache.lucene.LuceneService; import org.apache.geode.cache.lucene.LuceneServiceProvider; import org.apache.lucene.analysis.Analyzer; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.gemfire.config.annotation.IndexConfigurer; +import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport; import org.springframework.data.gemfire.util.CacheUtils; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * Spring {@link FactoryBean} used to construct {@link LuceneIndex Lucene Indexes} on application domain object fields. + * Spring {@link FactoryBean} used to construct, configure and initialize {@link LuceneIndex Lucene Indexes} + * on application domain object fields. * * @author John Blum - * @see org.springframework.beans.factory.BeanFactory - * @see org.springframework.beans.factory.BeanFactoryAware - * @see org.springframework.beans.factory.BeanNameAware - * @see org.springframework.beans.factory.DisposableBean - * @see org.springframework.beans.factory.FactoryBean - * @see org.springframework.beans.factory.InitializingBean * @see org.apache.geode.cache.GemFireCache * @see org.apache.geode.cache.Region * @see org.apache.geode.cache.lucene.LuceneIndex @@ -63,20 +61,33 @@ import org.springframework.util.StringUtils; * @see org.apache.geode.cache.lucene.LuceneService * @see org.apache.geode.cache.lucene.LuceneServiceProvider * @see org.apache.lucene.analysis.Analyzer + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.beans.factory.InitializingBean + * @see org.springframework.data.gemfire.config.annotation.IndexConfigurer + * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport * @since 1.1.0 */ @SuppressWarnings("unused") -public class LuceneIndexFactoryBean implements FactoryBean, - BeanFactoryAware, BeanNameAware, InitializingBean, DisposableBean { +public class LuceneIndexFactoryBean extends AbstractFactoryBeanSupport + implements DisposableBean, InitializingBean { protected static final boolean DEFAULT_DESTROY = false; private boolean destroy = DEFAULT_DESTROY; - private BeanFactory beanFactory; - private GemFireCache gemfireCache; + private List indexConfigurers = Collections.emptyList(); + + private IndexConfigurer compositeIndexConfigurer = new IndexConfigurer() { + + @Override + public void configure(String beanName, LuceneIndexFactoryBean bean) { + nullSafeCollection(indexConfigurers).forEach(indexConfigurer -> indexConfigurer.configure(beanName, bean)); + } + }; + private List fields; private LuceneIndex luceneIndex; @@ -95,13 +106,72 @@ public class LuceneIndexFactoryBean implements FactoryBean, */ @Override public void afterPropertiesSet() throws Exception { + String indexName = getIndexName(); + applyIndexConfigurers(indexName); + this.gemfireCache = resolveCache(); this.luceneService = resolveLuceneService(); this.regionPath = resolveRegionPath(); - setLuceneIndex(createIndex(indexName, getRegionPath())); + setLuceneIndex(resolveLuceneIndex(indexName, getRegionPath())); + } + + /* (non-Javadoc) */ + private void applyIndexConfigurers(String indexName) { + applyIndexConfigurers(indexName, getCompositeRegionConfigurer()); + } + + /** + * Null-safe operation to apply the given array of {@link IndexConfigurer IndexConfigurers} + * to this {@link LuceneIndexFactoryBean}. + * + * @param indexName {@link String} containing the name of the {@link LuceneIndex}. + * @param indexConfigurers array of {@link IndexConfigurer IndexConfigurers} applied + * to this {@link LuceneIndexFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + * @see #applyIndexConfigurers(String, Iterable) + */ + protected void applyIndexConfigurers(String indexName, IndexConfigurer... indexConfigurers) { + applyIndexConfigurers(indexName, Arrays.asList(nullSafeArray(indexConfigurers, IndexConfigurer.class))); + } + + /** + * Null-safe operation to apply the given {@link Iterable} of {@link IndexConfigurer IndexConfigurers} + * to this {@link LuceneIndexFactoryBean}. + * + * @param indexName {@link String} containing the name of the {@link LuceneIndex}. + * @param indexConfigurers {@link Iterable} of {@link IndexConfigurer IndexConfigurers} applied + * to this {@link LuceneIndexFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + */ + protected void applyIndexConfigurers(String indexName, Iterable indexConfigurers) { + stream(nullSafeIterable(indexConfigurers).spliterator(), false) + .forEach(indexConfigurer -> indexConfigurer.configure(indexName, this)); + } + + /** + * Attempts to resolve a {@link LuceneIndex} by the given {@link String indexName} first then attempts to create + * the {@link LuceneIndex} with the given {@link Region#getFullPath() Region path}. + * + * @param indexName {@link String name} of the {@link LuceneIndex} to resolve. + * @param regionPath {@link Region#getFullPath() Region path} on which the {@link LuceneIndex} is applied. + * @return the resolved {@link LuceneIndex} by the given {@link String indexName} or the created {@link LuceneIndex} + * with the given {@link Region#getFullPath() Region path} if the {@link LuceneIndex} could not be resolved by + * {@link String indexName}. + * @see org.apache.geode.cache.lucene.LuceneService#getIndex(String, String) + * @see #createLuceneIndex(String, String) + * @see #getLuceneIndex() + */ + protected LuceneIndex resolveLuceneIndex(String indexName, String regionPath) { + + Supplier luceneIndexSupplier = () -> + Optional.ofNullable(resolveLuceneService()) + .map(luceneService -> luceneService.getIndex(indexName, regionPath)) + .orElseGet(() -> createLuceneIndex(indexName, regionPath)); + + return getLuceneIndex().orElseGet(luceneIndexSupplier); } /** @@ -119,7 +189,8 @@ public class LuceneIndexFactoryBean implements FactoryBean, * @see #getFields() * @see #resolveFields(List) */ - protected LuceneIndex createIndex(String indexName, String regionPath) { + protected LuceneIndex createLuceneIndex(String indexName, String regionPath) { + LuceneService luceneService = resolveLuceneService(); LuceneIndexFactory indexFactory = luceneService.createIndexFactory(); @@ -153,6 +224,7 @@ public class LuceneIndexFactoryBean implements FactoryBean, @Override @SuppressWarnings("deprecation") public void destroy() throws Exception { + LuceneIndex luceneIndex = getObject(); if (isLuceneIndexDestroyable(luceneIndex)) { @@ -178,6 +250,7 @@ public class LuceneIndexFactoryBean implements FactoryBean, */ @Override public LuceneIndex getObject() throws Exception { + if (this.luceneIndex == null) { setLuceneIndex(Optional.ofNullable(resolveLuceneService()) .map((luceneService) -> luceneService.getIndex(getIndexName(), resolveRegionPath())) @@ -195,14 +268,6 @@ public class LuceneIndexFactoryBean implements FactoryBean, return Optional.ofNullable(this.luceneIndex).>map(LuceneIndex::getClass).orElse(LuceneIndex.class); } - /** - * @inheritDoc - */ - @Override - public boolean isSingleton() { - return true; - } - /** * Resolves a reference to the {@link GemFireCache}. * @@ -247,6 +312,7 @@ public class LuceneIndexFactoryBean implements FactoryBean, * @see #resolveLuceneService(GemFireCache) */ protected LuceneService resolveLuceneService() { + return Optional.ofNullable(getLuceneService()).orElseGet(() -> Optional.ofNullable(getBeanFactory()).map(beanFactory -> { try { @@ -283,6 +349,7 @@ public class LuceneIndexFactoryBean implements FactoryBean, * @see #getRegionPath() */ protected Region resolveRegion() { + return Optional.ofNullable(getRegion()).orElseGet(() -> { GemFireCache cache = resolveCache(); String regionPath = getRegionPath(); @@ -302,6 +369,7 @@ public class LuceneIndexFactoryBean implements FactoryBean, * @see #getRegionPath() */ protected String resolveRegionPath() { + String regionPath = Optional.ofNullable(resolveRegion()) .map(Region::getFullPath).orElseGet(this::getRegionPath); @@ -310,29 +378,12 @@ public class LuceneIndexFactoryBean implements FactoryBean, return regionPath; } - /** - * @inheritDoc - */ - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - /** - * Returns a reference to the containing Spring {@link BeanFactory} if set. - * - * @return a reference to the containing Spring {@link BeanFactory} if set. - * @see org.springframework.beans.factory.BeanFactory - */ - protected BeanFactory getBeanFactory() { - return this.beanFactory; - } - /** * @inheritDoc */ @Override public void setBeanName(String name) { + super.setBeanName(name); setIndexName(name); } @@ -357,6 +408,17 @@ public class LuceneIndexFactoryBean implements FactoryBean, return this.gemfireCache; } + /** + * Returns a reference to the Composite {@link IndexConfigurer} used to apply additional configuration + * to this {@link LuceneIndexFactoryBean} on Spring container initialization. + * + * @return the Composite {@link IndexConfigurer}. + * @see org.springframework.data.gemfire.config.annotation.IndexConfigurer + */ + protected IndexConfigurer getCompositeRegionConfigurer() { + return this.compositeIndexConfigurer; + } + /** * Sets whether to destroy the {@link LuceneIndex} on shutdown. * @@ -431,6 +493,31 @@ public class LuceneIndexFactoryBean implements FactoryBean, return nullSafeList(this.fields); } + /** + * Null-safe operation to set an array of {@link IndexConfigurer IndexConfigurers} used to apply + * additional configuration to this {@link LuceneIndexFactoryBean} when using Annotation-based configuration. + * + * @param indexConfigurers array of {@link IndexConfigurer IndexConfigurers} used to apply + * additional configuration to this {@link LuceneIndexFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.IndexConfigurer + * @see #setIndexConfigurers(List) + */ + public void setIndexConfigurers(IndexConfigurer... indexConfigurers) { + setIndexConfigurers(Arrays.asList(nullSafeArray(indexConfigurers, IndexConfigurer.class))); + } + + /** + * Null-safe operation to set an {@link Iterable} of {@link IndexConfigurer IndexConfigurers} used to apply + * additional configuration to this {@link LuceneIndexFactoryBean} when using Annotation-based configuration. + * + * @param indexConfigurers {@link Iterable } of {@link IndexConfigurer IndexConfigurers} used to apply + * additional configuration to this {@link LuceneIndexFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.IndexConfigurer + */ + public void setIndexConfigurers(List indexConfigurers) { + this.indexConfigurers = Optional.ofNullable(indexConfigurers).orElseGet(Collections::emptyList); + } + /** * Sets the name of the {@link LuceneIndex} as identified in the {@link GemFireCache}. * @@ -453,16 +540,27 @@ public class LuceneIndexFactoryBean implements FactoryBean, } /** - * Sets the {@link LuceneIndex} as the index created by this {@link FactoryBean}. + * Returns an {@link Optional} reference to the {@link LuceneIndex} created by this {@link LuceneIndexFactoryBean}. * - * This method is for testing purposes only! + * @return an {@link Optional} reference to the {@link LuceneIndex} created by this {@link LuceneIndexFactoryBean}. + * @see org.apache.geode.cache.lucene.LuceneIndex + * @see java.util.Optional + */ + public Optional getLuceneIndex() { + return Optional.ofNullable(this.luceneIndex); + } + + /** + * Sets the given {@link LuceneIndex} as the index created by this {@link FactoryBean}. + * + * This method is generally used for testing purposes only. * * @param luceneIndex {@link LuceneIndex} created by this {@link FactoryBean}. * @return this {@link LuceneIndexFactoryBean}. * @see org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean * @see org.apache.geode.cache.lucene.LuceneIndex */ - protected LuceneIndexFactoryBean setLuceneIndex(LuceneIndex luceneIndex) { + public LuceneIndexFactoryBean setLuceneIndex(LuceneIndex luceneIndex) { this.luceneIndex = luceneIndex; return this; } diff --git a/src/main/java/org/springframework/data/gemfire/server/CacheServerFactoryBean.java b/src/main/java/org/springframework/data/gemfire/server/CacheServerFactoryBean.java index 53f638e0..4a2f822b 100644 --- a/src/main/java/org/springframework/data/gemfire/server/CacheServerFactoryBean.java +++ b/src/main/java/org/springframework/data/gemfire/server/CacheServerFactoryBean.java @@ -15,12 +15,22 @@ */ package org.springframework.data.gemfire.server; +import static java.util.stream.StreamSupport.stream; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection; +import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable; +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; + import java.io.IOException; +import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.Optional; import java.util.Set; import org.apache.geode.cache.Cache; import org.apache.geode.cache.InterestRegistrationListener; +import org.apache.geode.cache.client.ClientCache; import org.apache.geode.cache.server.CacheServer; import org.apache.geode.cache.server.ClientSubscriptionConfig; import org.apache.geode.cache.server.ServerLoadProbe; @@ -29,18 +39,28 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.SmartLifecycle; -import org.springframework.util.Assert; +import org.springframework.data.gemfire.config.annotation.CacheServerConfigurer; +import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport; import org.springframework.util.StringUtils; /** - * FactoryBean for easy creation and configuration of GemFire {@link CacheServer} instances. + * Spring {@link FactoryBean} used to construct, configure and initialize a {@link CacheServer}. * * @author Costin Leau * @author John Blum + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.client.ClientCache + * @see org.apache.geode.cache.server.CacheServer + * @see org.springframework.beans.factory.DisposableBean + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.beans.factory.InitializingBean + * @see org.springframework.context.SmartLifecycle + * @see org.springframework.data.gemfire.config.annotation.CacheServerConfigurer + * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport */ @SuppressWarnings("unused") -public class CacheServerFactoryBean implements FactoryBean, - InitializingBean, DisposableBean, SmartLifecycle { +public class CacheServerFactoryBean extends AbstractFactoryBeanSupport + implements DisposableBean, InitializingBean, SmartLifecycle { private boolean autoStartup = true; private boolean notifyBySubscription = CacheServer.DEFAULT_NOTIFY_BY_SUBSCRIPTION; @@ -60,6 +80,12 @@ public class CacheServerFactoryBean implements FactoryBean, private CacheServer cacheServer; + private List cacheServerConfigurers = Collections.emptyList(); + + private CacheServerConfigurer compositeCacheServerConfigurer = (beanName, bean) -> + nullSafeCollection(cacheServerConfigurers).forEach(cacheServerConfigurer -> + cacheServerConfigurer.configure(beanName, bean)); + private ServerLoadProbe serverLoadProbe = CacheServer.DEFAULT_LOAD_PROBE; private Set listeners = Collections.emptySet(); @@ -75,68 +101,149 @@ public class CacheServerFactoryBean implements FactoryBean, /** * {@inheritDoc} */ + @Override @SuppressWarnings("deprecation") public void afterPropertiesSet() throws IOException { - Assert.notNull(cache, "A GemFire Cache is required."); - cacheServer = cache.addCacheServer(); - cacheServer.setBindAddress(bindAddress); - cacheServer.setGroups(serverGroups); - cacheServer.setHostnameForClients(hostNameForClients); - cacheServer.setLoadPollInterval(loadPollInterval); - cacheServer.setLoadProbe(serverLoadProbe); - cacheServer.setMaxConnections(maxConnections); - cacheServer.setMaximumMessageCount(maxMessageCount); - cacheServer.setMaximumTimeBetweenPings(maxTimeBetweenPings); - cacheServer.setMaxThreads(maxThreads); - cacheServer.setMessageTimeToLive(messageTimeToLive); - cacheServer.setNotifyBySubscription(notifyBySubscription); - cacheServer.setPort(port); - cacheServer.setSocketBufferSize(socketBufferSize); + applyCacheServerConfigurers(); - for (InterestRegistrationListener listener : listeners) { - cacheServer.registerInterestRegistrationListener(listener); - } + Cache cache = resolveCache(); - ClientSubscriptionConfig config = cacheServer.getClientSubscriptionConfig(); + this.cacheServer = postProcess(configure(addCacheServer(cache))); + } - config.setCapacity(subscriptionCapacity); - getSubscriptionEvictionPolicy().setEvictionPolicy(config); - - if (StringUtils.hasText(subscriptionDiskStore)) { - config.setDiskStoreName(subscriptionDiskStore); - } + /* (non-Javadoc) */ + private void applyCacheServerConfigurers() { + applyCacheServerConfigurers(getCompositeCacheServerConfigurer()); } /** - * {@inheritDoc} + * Null-safe operation to apply the given array of {@link CacheServerConfigurer CacheServerConfigurers} + * to this {@link CacheServerFactoryBean}. + * + * @param cacheServerConfigurers array of {@link CacheServerConfigurer CacheServerConfigurers} applied to + * this {@link CacheServerFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.CacheServerConfigurer + * @see #applyCacheServerConfigurers(Iterable) */ - public CacheServer getObject() { + protected void applyCacheServerConfigurers(CacheServerConfigurer... cacheServerConfigurers) { + applyCacheServerConfigurers(Arrays.asList(nullSafeArray(cacheServerConfigurers, CacheServerConfigurer.class))); + } + + /** + * Null-safe operation to apply the given {@link Iterable} of {@link CacheServerConfigurer CacheServerConfigurers} + * to this {@link CacheServerFactoryBean}. + * + * @param cacheServerConfigurers {@link Iterable} of {@link CacheServerConfigurer CacheServerConfigurers} applied to + * this {@link CacheServerFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.CacheServerConfigurer + */ + protected void applyCacheServerConfigurers(Iterable cacheServerConfigurers) { + stream(nullSafeIterable(cacheServerConfigurers).spliterator(), false) + .forEach(cacheServerConfigurer -> cacheServerConfigurer.configure(getBeanName(), this)); + } + + /* (non-Javadoc) */ + private Cache resolveCache() { + return Optional.ofNullable(this.cache) + .orElseThrow(() -> newIllegalArgumentException("Cache is required")); + } + + /** + * Adds a {@link CacheServer} to the given {@link Cache} for server {@link ClientCache cache clients}. + * + * @param cache {@link Cache} used to add a {@link CacheServer}. + * @return the newly added {@link CacheServer}. + * @see org.apache.geode.cache.Cache#addCacheServer() + * @see org.apache.geode.cache.server.CacheServer + */ + protected CacheServer addCacheServer(Cache cache) { + return cache.addCacheServer(); + } + + /** + * Configures the provided {@link CacheServer} with any custom, application-specific configuration. + * + * @param cacheServer {@link CacheServer} to configure. + * @return the given {@link CacheServer}. + * @see org.apache.geode.cache.server.CacheServer + */ + protected CacheServer configure(CacheServer cacheServer) { + + cacheServer.setBindAddress(this.bindAddress); + cacheServer.setGroups(this.serverGroups); + cacheServer.setHostnameForClients(this.hostNameForClients); + cacheServer.setLoadPollInterval(this.loadPollInterval); + cacheServer.setLoadProbe(this.serverLoadProbe); + cacheServer.setMaxConnections(this.maxConnections); + cacheServer.setMaximumMessageCount(this.maxMessageCount); + cacheServer.setMaximumTimeBetweenPings(this.maxTimeBetweenPings); + cacheServer.setMaxThreads(this.maxThreads); + cacheServer.setMessageTimeToLive(this.messageTimeToLive); + cacheServer.setNotifyBySubscription(this.notifyBySubscription); + cacheServer.setPort(this.port); + cacheServer.setSocketBufferSize(this.socketBufferSize); + + nullSafeCollection(this.listeners).forEach(cacheServer::registerInterestRegistrationListener); + + ClientSubscriptionConfig config = cacheServer.getClientSubscriptionConfig(); + + config.setCapacity(this.subscriptionCapacity); + getSubscriptionEvictionPolicy().setEvictionPolicy(config); + + Optional.ofNullable(this.subscriptionDiskStore).filter(StringUtils::hasText) + .ifPresent(config::setDiskStoreName); + return cacheServer; } /** - * {@inheritDoc} + * Post-process the {@link CacheServer} with any necessary follow-up actions. + * + * @param cacheServer {@link CacheServer} to process. + * @return the given {@link CacheServer}. + * @see org.apache.geode.cache.server.CacheServer */ - public Class getObjectType() { - return (this.cacheServer != null ? cacheServer.getClass() : CacheServer.class); + protected CacheServer postProcess(CacheServer cacheServer) { + return cacheServer; + } + + /** + * Returns a reference to the Composite {@link CacheServerConfigurer} used to apply additional configuration + * to this {@link CacheServerFactoryBean} on Spring container initialization. + * + * @return the Composite {@link CacheServerConfigurer}. + * @see org.springframework.data.gemfire.config.annotation.CacheServerConfigurer + */ + protected CacheServerConfigurer getCompositeCacheServerConfigurer() { + return this.compositeCacheServerConfigurer; } /** * {@inheritDoc} */ - public boolean isSingleton() { - return true; + @Override + public CacheServer getObject() { + return this.cacheServer; + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public Class getObjectType() { + return Optional.ofNullable(this.cacheServer).map(CacheServer::getClass).orElse((Class) CacheServer.class); } /* (non-Javadoc) */ public boolean isRunning() { - return (cacheServer != null && cacheServer.isRunning()); + return Optional.ofNullable(this.cacheServer).map(CacheServer::isRunning).orElse(false); } /* (non-Javadoc) */ public boolean isAutoStartup() { - return autoStartup; + return this.autoStartup; } /** @@ -149,10 +256,11 @@ public class CacheServerFactoryBean implements FactoryBean, /* (non-Javadoc) */ public void destroy() { stop(); - cacheServer = null; + this.cacheServer = null; } /* (non-Javadoc) */ + @Override public void start() { try { cacheServer.start(); @@ -163,16 +271,15 @@ public class CacheServerFactoryBean implements FactoryBean, } /* (non-Javadoc) */ - public void stop(final Runnable callback) { + @Override + public void stop(Runnable callback) { stop(); callback.run(); } /* (non-Javadoc) */ public void stop() { - if (cacheServer != null) { - cacheServer.stop(); - } + Optional.ofNullable(this.cacheServer).ifPresent(CacheServer::stop); } /* (non-Javadoc) */ @@ -198,6 +305,32 @@ public class CacheServerFactoryBean implements FactoryBean, this.cacheServer = cacheServer; } + /** + * Null-safe operation to set an array of {@link CacheServerConfigurer CacheServerConfigurers} used to apply + * additional configuration to this {@link CacheServerFactoryBean} when using Annotation-based configuration. + * + * @param cacheServerConfigurers array of {@link CacheServerConfigurer CacheServerConfigurers} used to apply + * additional configuration to this {@link CacheServerFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer + * @see #setCacheServerConfigurers(List) + */ + public void setCacheServerConfigurers(CacheServerConfigurer... cacheServerConfigurers) { + setCacheServerConfigurers(Arrays.asList(nullSafeArray(cacheServerConfigurers, CacheServerConfigurer.class))); + } + + /** + * Null-safe operation to set an {@link Iterable} of {@link CacheServerConfigurer CacheServerConfigurers} + * used to apply additional configuration to this {@link CacheServerFactoryBean} when using + * Annotation-based configuration. + * + * @param cacheServerConfigurers {@literal Iterable} of {@link CacheServerConfigurer CacheServerConfigurers} + * used to apply additional configuration to this {@link CacheServerFactoryBean}. + * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer + */ + public void setCacheServerConfigurers(List cacheServerConfigurers) { + this.cacheServerConfigurers = Optional.ofNullable(cacheServerConfigurers).orElseGet(Collections::emptyList); + } + /* (non-Javadoc) */ public void setHostNameForClients(String hostNameForClients) { this.hostNameForClients = hostNameForClients; @@ -275,7 +408,7 @@ public class CacheServerFactoryBean implements FactoryBean, /* (non-Javadoc) */ SubscriptionEvictionPolicy getSubscriptionEvictionPolicy() { - return (subscriptionEvictionPolicy != null ? subscriptionEvictionPolicy : SubscriptionEvictionPolicy.DEFAULT); + return Optional.ofNullable(this.subscriptionEvictionPolicy).orElse(SubscriptionEvictionPolicy.DEFAULT); } /* (non-Javadoc) */ diff --git a/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java b/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java new file mode 100644 index 00000000..0a1ca91f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupport.java @@ -0,0 +1,206 @@ +/* + * Copyright 2017 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.support; + +import java.util.Optional; +import java.util.function.Supplier; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.FactoryBean; + +/** + * The {@link AbstractFactoryBeanSupport} class is an abstract Spring {@link FactoryBean} base class implementation + * encapsulating operations common to SDG's {@link FactoryBean} implementations. + * + * @author John Blum + * @see org.springframework.beans.factory.BeanClassLoaderAware + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.beans.factory.BeanFactoryAware + * @see org.springframework.beans.factory.BeanNameAware + * @see org.springframework.beans.factory.FactoryBean + * @since 1.0.0 + */ +public abstract class AbstractFactoryBeanSupport implements FactoryBean, + BeanClassLoaderAware, BeanFactoryAware, BeanNameAware { + + protected static final boolean DEFAULT_SINGLETON = true; + + private ClassLoader beanClassLoader; + + private BeanFactory beanFactory; + + private final Log log = newLog(); + + private String beanName; + + /** + * Constructs a new instance of {@link Log} to log statements printed by Spring Data GemFire/Geode. + * + * @return a new instance of {@link Log}. + * @see org.apache.commons.logging.LogFactory#getLog(Class) + * @see org.apache.commons.logging.Log + */ + protected Log newLog() { + return LogFactory.getLog(getClass()); + } + + /** + * Sets a reference to the {@link ClassLoader} used by the Spring container to load and create bean classes. + * + * @param classLoader {@link ClassLoader} used by the Spring container to load and create bean classes. + * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(ClassLoader) + * @see java.lang.ClassLoader + */ + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + /** + * Returns a reference to the {@link ClassLoader} used by the Spring container to load and create bean classes. + * + * @return the {@link ClassLoader} used by the Spring container to load and create bean classes. + * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(ClassLoader) + * @see java.lang.ClassLoader + */ + public ClassLoader getBeanClassLoader() { + return this.beanClassLoader; + } + + /** + * Sets a reference to the Spring {@link BeanFactory} in which this {@link FactoryBean} was declared. + * + * @param beanFactory reference to the declaring Spring {@link BeanFactory}. + * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(BeanFactory) + * @see org.springframework.beans.factory.BeanFactory + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + /** + * Returns a reference to the Spring {@link BeanFactory} in which this {@link FactoryBean} was declared. + * + * @return a reference to the declaring Spring {@link BeanFactory}. + * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(BeanFactory) + * @see org.springframework.beans.factory.BeanFactory + */ + public BeanFactory getBeanFactory() { + return this.beanFactory; + } + + /** + * Sets the {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container. + * + * @param name {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container. + * @see org.springframework.beans.factory.BeanNameAware#setBeanName(String) + * @see java.lang.String + */ + @Override + public void setBeanName(String name) { + this.beanName = name; + } + + /** + * Returns the {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container. + * + * @return the {@link String bean name} assigned to this {@link FactoryBean} as declared in the Spring container. + * @see org.springframework.beans.factory.BeanNameAware#setBeanName(String) + * @see java.lang.String + */ + public String getBeanName() { + return this.beanName; + } + + /** + * Returns a reference to the {@link Log} used by this {@link FactoryBean} to log {@link String messages}. + * + * @return a reference to the {@link Log} used by this {@link FactoryBean} to log {@link String messages}. + * @see org.apache.commons.logging.Log + */ + protected Log getLog() { + return this.log; + } + + /** + * Indicates that this {@link FactoryBean} produces a single bean instance. + * + * @return {@literal true} by default. + * @see org.springframework.beans.factory.FactoryBean#isSingleton() + */ + @Override + public boolean isSingleton() { + return DEFAULT_SINGLETON; + } + + /** + * Logs the {@link String message} formatted with the array of {@link Object arguments} at debug level. + * + * @param message {@link String} containing the message to log. + * @param args array of {@link Object arguments} used to format the {@code message}. + * @see #logDebug(Supplier) + */ + protected void logDebug(String message, Object... args) { + logDebug(() -> String.format(message, args)); + } + + /** + * Logs the {@link String message} supplied by the given {@link Supplier} at debug level. + * + * @param message {@link Supplier} containing the {@link String message} and arguments to log. + * @see org.apache.commons.logging.Log#isDebugEnabled() + * @see org.apache.commons.logging.Log#debug(Object) + * @see #getLog() + */ + protected void logDebug(Supplier message) { + Optional.ofNullable(getLog()) + .filter(Log::isDebugEnabled) + .ifPresent(log -> log.debug(message.get())); + } + + /** + * Logs the {@link String message} formatted with the array of {@link Object arguments} at info level. + * + * @param message {@link String} containing the message to log. + * @param args array of {@link Object arguments} used to format the {@code message}. + * @see #logInfo(Supplier) + */ + protected void logInfo(String message, Object... args) { + logInfo(() -> String.format(message, args)); + } + + /** + * Logs the {@link String message} supplied by the given {@link Supplier} at info level. + * + * @param message {@link Supplier} containing the {@link String message} and arguments to log. + * @see org.apache.commons.logging.Log#isInfoEnabled() + * @see org.apache.commons.logging.Log#info(Object) + * @see #getLog() + */ + protected void logInfo(Supplier message) { + Optional.ofNullable(getLog()) + .filter(Log::isInfoEnabled) + .ifPresent(log -> log.info(message.get())); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java b/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java index 2049fa35..adda07e7 100644 --- a/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java +++ b/src/main/java/org/springframework/data/gemfire/util/CacheUtils.java @@ -17,6 +17,8 @@ package org.springframework.data.gemfire.util; +import java.util.Optional; + import org.apache.geode.cache.Cache; import org.apache.geode.cache.CacheClosedException; import org.apache.geode.cache.CacheFactory; @@ -29,11 +31,10 @@ import org.apache.geode.internal.cache.GemFireCacheImpl; import org.springframework.util.StringUtils; /** - * CacheUtils is an abstract utility class encapsulating common operations for working with GemFire Cache - * and ClientCache instances. + * {@link CacheUtils} is an abstract utility class encapsulating common operations for working with + * {@link Cache} and {@link ClientCache} instances. * * @author John Blum - * @see org.springframework.data.gemfire.util.DistributedSystemUtils * @see org.apache.geode.cache.Cache * @see org.apache.geode.cache.CacheFactory * @see org.apache.geode.cache.GemFireCache @@ -42,6 +43,7 @@ import org.springframework.util.StringUtils; * @see org.apache.geode.cache.client.ClientCacheFactory * @see org.apache.geode.distributed.DistributedSystem * @see org.apache.geode.internal.cache.GemFireCacheImpl + * @see org.springframework.data.gemfire.util.DistributedSystemUtils * @since 1.8.0 */ @SuppressWarnings("unused") @@ -52,6 +54,7 @@ public abstract class CacheUtils extends DistributedSystemUtils { /* (non-Javadoc) */ @SuppressWarnings("all") public static boolean isClient(GemFireCache cache) { + boolean client = (cache instanceof ClientCache); if (cache instanceof GemFireCacheImpl) { @@ -63,6 +66,7 @@ public abstract class CacheUtils extends DistributedSystemUtils { /* (non-Javadoc) */ public static boolean isDurable(ClientCache clientCache) { + DistributedSystem distributedSystem = getDistributedSystem(clientCache); // NOTE technically the following code snippet would be more useful/valuable but is not "testable"! @@ -75,6 +79,7 @@ public abstract class CacheUtils extends DistributedSystemUtils { /* (non-Javadoc) */ @SuppressWarnings("all") public static boolean isPeer(GemFireCache cache) { + boolean peer = (cache instanceof Cache); if (cache instanceof GemFireCacheImpl) { @@ -128,7 +133,7 @@ public abstract class CacheUtils extends DistributedSystemUtils { /* (non-Javadoc) */ public static GemFireCache resolveGemFireCache() { - return defaultIfNull(getCache(), CacheUtils::getClientCache); + return Optional.ofNullable(getCache()).orElseGet(CacheUtils::getClientCache); } /* (non-Javadoc) */ diff --git a/src/test/java/org/springframework/data/gemfire/CacheFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/CacheFactoryBeanTest.java index c85756d3..f61e2f92 100644 --- a/src/test/java/org/springframework/data/gemfire/CacheFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/CacheFactoryBeanTest.java @@ -16,12 +16,10 @@ package org.springframework.data.gemfire; -import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -57,11 +55,13 @@ import org.apache.geode.cache.control.ResourceManager; import org.apache.geode.cache.util.GatewayConflictResolver; import org.apache.geode.distributed.DistributedMember; import org.apache.geode.distributed.DistributedSystem; -import org.apache.geode.distributed.Role; import org.apache.geode.pdx.PdxSerializer; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import org.springframework.beans.factory.BeanFactory; import org.springframework.core.io.Resource; import org.springframework.data.gemfire.support.GemfireBeanFactoryLocator; @@ -73,13 +73,27 @@ import org.springframework.data.util.ReflectionUtils; * @author John Blum * @see org.junit.Rule * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.mockito.Mock * @see org.mockito.Mockito - * @see org.springframework.data.gemfire.CacheFactoryBean + * @see org.mockito.junit.MockitoJUnitRunner * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.CacheFactory + * @see org.apache.geode.cache.CacheTransactionManager + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.distributed.DistributedMember + * @see org.apache.geode.distributed.DistributedSystem + * @see org.apache.geode.pdx.PdxSerializer + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.data.gemfire.CacheFactoryBean * @since 1.7.0 */ +@RunWith(MockitoJUnitRunner.class) public class CacheFactoryBeanTest { + @Mock + private Cache mockCache; + @Rule public ExpectedException exception = ExpectedException.none(); @@ -89,7 +103,9 @@ public class CacheFactoryBeanTest { final Properties gemfireProperties = new Properties(); CacheFactoryBean cacheFactoryBean = new CacheFactoryBean() { - @Override protected void postProcessBeforeCacheInitialization(Properties actualGemfireProperties) { + + @Override + protected void postProcessBeforeCacheInitialization(Properties actualGemfireProperties) { assertThat(actualGemfireProperties, is(sameInstance(gemfireProperties))); postProcessBeforeCacheInitializationCalled.set(true); } @@ -169,7 +185,7 @@ public class CacheFactoryBeanTest { final AtomicBoolean initCalled = new AtomicBoolean(false); CacheFactoryBean cacheFactoryBean = new CacheFactoryBean() { - @Override Cache init() throws Exception { + @Override Cache init() { initCalled.set(true); return mockCache; } @@ -194,6 +210,7 @@ public class CacheFactoryBeanTest { } @Test + @SuppressWarnings("deprecation") public void init() throws Exception { BeanFactory mockBeanFactory = mock(BeanFactory.class); Cache mockCache = mock(Cache.class); @@ -218,8 +235,8 @@ public class CacheFactoryBeanTest { when(mockDistributedSystem.getDistributedMember()).thenReturn(mockDistributedMember); when(mockDistributedSystem.getName()).thenReturn("MockDistributedSystem"); when(mockDistributedMember.getId()).thenReturn("MockDistributedMember"); - when(mockDistributedMember.getGroups()).thenReturn(Collections.emptyList()); - when(mockDistributedMember.getRoles()).thenReturn(Collections.emptySet()); + when(mockDistributedMember.getGroups()).thenReturn(Collections.emptyList()); + when(mockDistributedMember.getRoles()).thenReturn(Collections.emptySet()); when(mockDistributedMember.getHost()).thenReturn("skullbox"); when(mockDistributedMember.getProcessId()).thenReturn(12345); @@ -392,8 +409,7 @@ public class CacheFactoryBeanTest { public void prepareFactoryWithUnspecifiedPdxOptions() { CacheFactory mockCacheFactory = mock(CacheFactory.class); - assertThat((CacheFactory) new CacheFactoryBean().prepareFactory(mockCacheFactory), - is(sameInstance(mockCacheFactory))); + assertThat(new CacheFactoryBean().prepareFactory(mockCacheFactory), is(sameInstance(mockCacheFactory))); verify(mockCacheFactory, never()).setPdxDiskStore(any(String.class)); verify(mockCacheFactory, never()).setPdxIgnoreUnreadFields(any(Boolean.class)); @@ -412,8 +428,7 @@ public class CacheFactoryBeanTest { CacheFactory mockCacheFactory = mock(CacheFactory.class); - assertThat((CacheFactory) cacheFactoryBean.prepareFactory(mockCacheFactory), - is(sameInstance(mockCacheFactory))); + assertThat(cacheFactoryBean.prepareFactory(mockCacheFactory), is(sameInstance(mockCacheFactory))); verify(mockCacheFactory, never()).setPdxDiskStore(any(String.class)); verify(mockCacheFactory, times(1)).setPdxIgnoreUnreadFields(eq(false)); @@ -434,8 +449,7 @@ public class CacheFactoryBeanTest { CacheFactory mockCacheFactory = mock(CacheFactory.class); - assertThat((CacheFactory) cacheFactoryBean.prepareFactory(mockCacheFactory), - is(sameInstance(mockCacheFactory))); + assertThat(cacheFactoryBean.prepareFactory(mockCacheFactory), is(sameInstance(mockCacheFactory))); verify(mockCacheFactory, times(1)).setPdxDiskStore(eq("testPdxDiskStoreName")); verify(mockCacheFactory, times(1)).setPdxIgnoreUnreadFields(eq(false)); @@ -444,52 +458,25 @@ public class CacheFactoryBeanTest { verify(mockCacheFactory, times(1)).setPdxSerializer(any(PdxSerializer.class)); } - @Test - public void prepareFactoryWithInvalidTypeForPdxSerializer() { - CacheFactory mockCacheFactory = mock(CacheFactory.class); - - Object pdxSerializer = new Object(); - - try { - CacheFactoryBean cacheFactoryBean = new CacheFactoryBean(); - - cacheFactoryBean.setPdxSerializer(pdxSerializer); - cacheFactoryBean.setPdxIgnoreUnreadFields(false); - cacheFactoryBean.setPdxReadSerialized(true); - - exception.expect(IllegalArgumentException.class); - exception.expectCause(is(nullValue(Throwable.class))); - exception.expectMessage(containsString(String.format( - "[%1$s] of type [java.lang.Object] is not a PdxSerializer", pdxSerializer))); - - cacheFactoryBean.prepareFactory(mockCacheFactory); - } - finally { - verify(mockCacheFactory, never()).setPdxSerializer(any(PdxSerializer.class)); - verify(mockCacheFactory, never()).setPdxDiskStore(any(String.class)); - verify(mockCacheFactory, never()).setPdxIgnoreUnreadFields(any(Boolean.class)); - verify(mockCacheFactory, never()).setPdxPersistent(any(Boolean.class)); - verify(mockCacheFactory, never()).setPdxReadSerialized(any(Boolean.class)); - } - } - @Test public void createCacheWithExistingCache() throws Exception { - Cache mockCache = mock(Cache.class); + CacheFactory mockCacheFactory = mock(CacheFactory.class); CacheFactoryBean cacheFactoryBean = new CacheFactoryBean(); cacheFactoryBean.setCache(mockCache); - Cache actualCache = cacheFactoryBean.createCache(null); + assertThat(cacheFactoryBean.getCache(), is(sameInstance(mockCache))); + + Cache actualCache = cacheFactoryBean.createCache(mockCacheFactory); assertThat(actualCache, is(sameInstance(mockCache))); + verify(mockCacheFactory, never()).create(); verifyZeroInteractions(mockCache); } @Test public void createCacheWithNoExistingCache() { - Cache mockCache = mock(Cache.class); CacheFactory mockCacheFactory = mock(CacheFactory.class); when(mockCacheFactory.create()).thenReturn(mockCache); @@ -510,13 +497,17 @@ public class CacheFactoryBeanTest { CacheFactoryBean cacheFactoryBean = new CacheFactoryBean(); cacheFactoryBean.setCriticalHeapPercentage(200.0f); - cacheFactoryBean.postProcess(null); + cacheFactoryBean.postProcess(mockCache); } catch (IllegalArgumentException expected) { - assertEquals("'criticalHeapPercentage' (200.0) is invalid; must be > 0.0 and <= 100.0", + assertEquals("criticalHeapPercentage [200.0] is not valid; must be > 0.0 and <= 100.0", expected.getMessage()); + throw expected; } + finally { + verifyZeroInteractions(mockCache); + } } @Test(expected = IllegalArgumentException.class) @@ -525,19 +516,23 @@ public class CacheFactoryBeanTest { CacheFactoryBean cacheFactoryBean = new CacheFactoryBean(); cacheFactoryBean.setEvictionHeapPercentage(-75.0f); - cacheFactoryBean.postProcess(null); + cacheFactoryBean.postProcess(mockCache); } catch (IllegalArgumentException expected) { - assertEquals("'evictionHeapPercentage' (-75.0) is invalid; must be > 0.0 and <= 100.0", + assertEquals("evictionHeapPercentage [-75.0] is not valid; must be > 0.0 and <= 100.0", expected.getMessage()); + throw expected; } + finally { + verifyZeroInteractions(mockCache); + } } @Test @SuppressWarnings("unchecked") public void getObjectType() { - assertThat((Class) new CacheFactoryBean().getObjectType(), is(equalTo(Cache.class))); + assertThat(new CacheFactoryBean().getObjectType(), is(equalTo(Cache.class))); } @Test @@ -686,10 +681,10 @@ public class CacheFactoryBeanTest { assertTrue(Boolean.FALSE.equals(TestUtils.readField("useBeanFactoryLocator", cacheFactoryBean))); assertTrue(Boolean.FALSE.equals(TestUtils.readField("close", cacheFactoryBean))); assertTrue(cacheFactoryBean.getCopyOnRead()); - assertEquals(0.95f, cacheFactoryBean.getCriticalHeapPercentage().floatValue(), 0.0f); + assertEquals(0.95f, cacheFactoryBean.getCriticalHeapPercentage(), 0.0f); assertNotNull(cacheFactoryBean.getDynamicRegionSupport()); assertTrue(cacheFactoryBean.getEnableAutoReconnect()); - assertEquals(0.70f, cacheFactoryBean.getEvictionHeapPercentage().floatValue(), 0.0f); + assertEquals(0.70f, cacheFactoryBean.getEvictionHeapPercentage(), 0.0f); assertSame(mockGatewayConflictResolver, cacheFactoryBean.getGatewayConflictResolver()); assertNotNull(cacheFactoryBean.getJndiDataSources()); assertEquals(1, cacheFactoryBean.getJndiDataSources().size()); diff --git a/src/test/java/org/springframework/data/gemfire/DiskStoreFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/DiskStoreFactoryBeanTest.java index 5187daca..890dd43c 100644 --- a/src/test/java/org/springframework/data/gemfire/DiskStoreFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/DiskStoreFactoryBeanTest.java @@ -22,11 +22,11 @@ import org.junit.Before; import org.junit.Test; /** - * The DiskStoreFactoryBeanTest class is a test suite of test cases testing the contract and functionality of the - * DiskStoreFactoryBean class. + * Unit tests for {@link DiskStoreFactoryBean}. * * @author John Blum * @see org.junit.Test + * @see org.apache.geode.cache.DiskStore * @see org.springframework.data.gemfire.DiskStoreFactoryBean * @since 1.3.4 */ @@ -58,7 +58,7 @@ public class DiskStoreFactoryBeanTest { } catch (IllegalArgumentException expected) { assertEquals(String.format("The DiskStore's (%1$s) compaction threshold (%2$d) must be an integer value between 0 and 100 inclusive.", - factoryBean.getName(), -1), expected.getMessage()); + factoryBean.resolveDiskStoreName(), -1), expected.getMessage()); throw expected; } } @@ -70,7 +70,7 @@ public class DiskStoreFactoryBeanTest { } catch (IllegalArgumentException expected) { assertEquals(String.format("The DiskStore's (%1$s) compaction threshold (%2$d) must be an integer value between 0 and 100 inclusive.", - factoryBean.getName(), 101), expected.getMessage()); + factoryBean.resolveDiskStoreName(), 101), expected.getMessage()); throw expected; } } @@ -88,7 +88,7 @@ public class DiskStoreFactoryBeanTest { catch (IllegalArgumentException expected) { assertEquals(String.format( "The DiskStore's (%1$s) compaction threshold (%2$d) must be an integer value between 0 and 100 inclusive.", - factoryBean.getName(), 200), expected.getMessage()); + factoryBean.resolveDiskStoreName(), 200), expected.getMessage()); throw expected; } } @@ -99,9 +99,8 @@ public class DiskStoreFactoryBeanTest { factoryBean.afterPropertiesSet(); } catch (IllegalStateException expected) { - assertEquals("A reference to the GemFire Cache must be set for Disk Store 'testDiskStore'.", expected.getMessage()); + assertEquals("Cache is required to create DiskStore [testDiskStore]", expected.getMessage()); throw expected; } } - } diff --git a/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java index dd7154e1..1140f97b 100644 --- a/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/IndexFactoryBeanTest.java @@ -22,10 +22,24 @@ import static org.hamcrest.CoreMatchers.isA; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Arrays; @@ -55,16 +69,14 @@ import org.springframework.data.gemfire.config.xml.GemfireConstants; import org.springframework.data.util.ReflectionUtils; /** - * The IndexFactoryBeanTest class is a test suite of test cases testing the contract and functionality - * of the IndexFactoryBean class. + * Unit tests for {@link IndexFactoryBean}. * * @author John Blum * @see org.junit.Test - * @see org.springframework.data.gemfire.IndexFactoryBean * @see org.apache.geode.cache.Cache * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.query.Index - * @see org.apache.geode.cache.query.QueryService + * @see org.springframework.data.gemfire.IndexFactoryBean * @since 1.5.2 */ public class IndexFactoryBeanTest { @@ -136,10 +148,12 @@ public class IndexFactoryBeanTest { @Test(expected = IllegalArgumentException.class) public void afterPropertiesSetWithNullCache() throws Exception { try { - new IndexFactoryBean().afterPropertiesSet(); + IndexFactoryBean indexFactoryBean = new IndexFactoryBean(); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.afterPropertiesSet(); } catch (IllegalArgumentException expected) { - assertEquals("The GemFire Cache reference must not be null!", expected.getMessage()); + assertEquals("Cache is required", expected.getMessage()); throw expected; } } @@ -154,6 +168,7 @@ public class IndexFactoryBeanTest { }; indexFactoryBean.setCache(mockCache); + indexFactoryBean.setName("TestIndex"); indexFactoryBean.afterPropertiesSet(); } catch (IllegalArgumentException expected) { @@ -165,10 +180,12 @@ public class IndexFactoryBeanTest { @Test(expected = IllegalArgumentException.class) public void afterPropertiesSetWithUnspecifiedExpression() throws Exception { try { - newIndexFactoryBean().afterPropertiesSet(); + IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); + indexFactoryBean.setName("TestIndex"); + indexFactoryBean.afterPropertiesSet(); } catch (IllegalArgumentException expected) { - assertEquals("Index 'expression' is required", expected.getMessage()); + assertEquals("Index expression is required", expected.getMessage()); throw expected; } } @@ -177,11 +194,12 @@ public class IndexFactoryBeanTest { public void afterPropertiesSetWithUnspecifiedFromClause() throws Exception { expectedException.expect(IllegalArgumentException.class); expectedException.expectCause(is(nullValue(Throwable.class))); - expectedException.expectMessage("Index 'from clause' is required"); + expectedException.expectMessage("Index from clause is required"); IndexFactoryBean indexFactoryBean = newIndexFactoryBean(); indexFactoryBean.setExpression("id"); + indexFactoryBean.setName("TestIndex"); indexFactoryBean.afterPropertiesSet(); } @@ -194,7 +212,7 @@ public class IndexFactoryBeanTest { indexFactoryBean.afterPropertiesSet(); } catch (IllegalArgumentException expected) { - assertEquals("Index 'name' is required", expected.getMessage()); + assertEquals("Index name is required", expected.getMessage()); throw expected; } } @@ -211,7 +229,7 @@ public class IndexFactoryBeanTest { indexFactoryBean.afterPropertiesSet(); } catch (IllegalArgumentException expected) { - assertEquals("'imports' are not supported with a KEY Index", expected.getMessage()); + assertEquals("imports are not supported with a KEY Index", expected.getMessage()); throw expected; } } @@ -1076,5 +1094,4 @@ public class IndexFactoryBeanTest { throw expected; } } - } diff --git a/src/test/java/org/springframework/data/gemfire/PartitionedRegionFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/PartitionedRegionFactoryBeanTest.java index d3dbf70f..2c277814 100644 --- a/src/test/java/org/springframework/data/gemfire/PartitionedRegionFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/PartitionedRegionFactoryBeanTest.java @@ -169,7 +169,7 @@ public class PartitionedRegionFactoryBeanTest { factoryBean.resolveDataPolicy(mockRegionFactory, true, "PARTITION"); } catch (IllegalArgumentException e) { - assertEquals("Data Policy 'PARTITION' is invalid when persistent is true.", e.getMessage()); + assertEquals("Data Policy [PARTITION] is invalid when persistent is true.", e.getMessage()); throw e; } finally { @@ -195,7 +195,7 @@ public class PartitionedRegionFactoryBeanTest { factoryBean.resolveDataPolicy(mockRegionFactory, false, "PERSISTENT_PARTITION"); } catch (IllegalArgumentException e) { - assertEquals("Data Policy 'PERSISTENT_PARTITION' is invalid when persistent is false.", e.getMessage()); + assertEquals("Data Policy [PERSISTENT_PARTITION] is invalid when persistent is false.", e.getMessage()); throw e; } finally { @@ -212,5 +212,4 @@ public class PartitionedRegionFactoryBeanTest { factoryBean.resolveDataPolicy(mockRegionFactory, true, "PERSISTENT_PARTITION"); verify(mockRegionFactory).setDataPolicy(eq(DataPolicy.PERSISTENT_PARTITION)); } - } diff --git a/src/test/java/org/springframework/data/gemfire/RegionFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/RegionFactoryBeanTest.java index 602a5875..dd0c1ccd 100644 --- a/src/test/java/org/springframework/data/gemfire/RegionFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/RegionFactoryBeanTest.java @@ -57,12 +57,10 @@ import org.springframework.data.gemfire.test.support.AbstractRegionFactoryBeanTe import org.springframework.data.gemfire.util.ArrayUtils; /** - * The RegionFactoryBeanTest class is a test suite of test cases testing the contract and functionality of the - * RegionFactoryBean class. + * Unit tests for {@link RegionFactoryBean}. * * @author David Turanski * @author John Blum - * @see org.springframework.data.gemfire.RegionFactoryBean * @see org.apache.geode.cache.Cache * @see org.apache.geode.cache.DataPolicy * @see org.apache.geode.cache.PartitionAttributes @@ -70,6 +68,8 @@ import org.springframework.data.gemfire.util.ArrayUtils; * @see org.apache.geode.cache.RegionAttributes * @see org.apache.geode.cache.RegionFactory * @see org.apache.geode.cache.RegionShortcut + * @see org.springframework.data.gemfire.RegionFactoryBean + * @see org.springframework.data.gemfire.test.support.AbstractRegionFactoryBeanTests */ @SuppressWarnings("unchecked") public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { @@ -125,7 +125,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { @Override public void verify() { assertNotNull(this.exception); - assertEquals("Data Policy 'PERSISTENT_REPLICATE' is invalid when persistent is false.", + assertEquals("Data Policy [PERSISTENT_REPLICATE] is invalid when persistent is false.", exception.getMessage()); } }; @@ -195,7 +195,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { factoryBean.assertDataPolicyAndPersistentAttributesAreCompatible(DataPolicy.REPLICATE); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'REPLICATE' is invalid when persistent is true.", expected.getMessage()); + assertEquals("Data Policy [REPLICATE] is invalid when persistent is true.", expected.getMessage()); throw expected; } } @@ -208,7 +208,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { factoryBean.assertDataPolicyAndPersistentAttributesAreCompatible(DataPolicy.PERSISTENT_PARTITION); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'PERSISTENT_PARTITION' is invalid when persistent is false.", + assertEquals("Data Policy [PERSISTENT_PARTITION] is invalid when persistent is false.", expected.getMessage()); throw expected; } @@ -724,7 +724,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { factoryBean.resolveDataPolicy(mockRegionFactory, true, " "); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy ' ' is invalid.", expected.getMessage()); + assertEquals("Data Policy [ ] is invalid.", expected.getMessage()); throw expected; } finally { @@ -743,7 +743,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { factoryBean.resolveDataPolicy(mockRegionFactory, true, ""); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy '' is invalid.", expected.getMessage()); + assertEquals("Data Policy [] is invalid.", expected.getMessage()); throw expected; } finally { @@ -762,7 +762,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { factoryBean.resolveDataPolicy(mockRegionFactory, true, "CSV"); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'CSV' is invalid.", expected.getMessage()); + assertEquals("Data Policy [CSV] is invalid.", expected.getMessage()); throw expected; } finally { @@ -796,7 +796,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { factoryBean.resolveDataPolicy(mockRegionFactory, true, "EMPTY"); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'EMPTY' is invalid when persistent is true.", expected.getMessage()); + assertEquals("Data Policy [EMPTY] is invalid when persistent is true.", expected.getMessage()); throw expected; } finally { @@ -830,7 +830,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { factoryBean.resolveDataPolicy(mockRegionFactory, false, "PERSISTENT_PARTITION"); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'PERSISTENT_PARTITION' is invalid when persistent is false.", expected.getMessage()); + assertEquals("Data Policy [PERSISTENT_PARTITION] is invalid when persistent is false.", expected.getMessage()); throw expected; } finally { @@ -852,7 +852,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { "Setting the 'persistent' attribute to TRUE and 'Data Policy' to PARTITION should have thrown an IllegalArgumentException!"); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'PARTITION' is invalid when persistent is true.", expected.getMessage()); + assertEquals("Data Policy [PARTITION] is invalid when persistent is true.", expected.getMessage()); throw expected; } finally { @@ -922,7 +922,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { factoryBean.resolveDataPolicy(mockRegionFactory, false, (String) null); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'PERSISTENT_PARTITION' is invalid when persistent is false.", expected.getMessage()); + assertEquals("Data Policy [PERSISTENT_PARTITION] is invalid when persistent is false.", expected.getMessage()); throw expected; } finally { @@ -943,7 +943,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { factoryBean.resolveDataPolicy(mockRegionFactory, true, (String) null); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'PARTITION' is invalid when persistent is true.", expected.getMessage()); + assertEquals("Data Policy [PARTITION] is invalid when persistent is true.", expected.getMessage()); throw expected; } finally { @@ -1005,7 +1005,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { fail("Setting the 'persistent' attribute to FALSE and 'Data Policy' to PERSISTENT_REPLICATE should have thrown an IllegalArgumentException!"); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'PERSISTENT_REPLICATE' is invalid when persistent is false.", expected.getMessage()); + assertEquals("Data Policy [PERSISTENT_REPLICATE] is invalid when persistent is false.", expected.getMessage()); throw expected; } finally { @@ -1026,7 +1026,7 @@ public class RegionFactoryBeanTest extends AbstractRegionFactoryBeanTests { fail("Setting the 'persistent' attribute to TRUE and 'Data Policy' to REPLICATE should have thrown an IllegalArgumentException!"); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'REPLICATE' is invalid when persistent is true.", expected.getMessage()); + assertEquals("Data Policy [REPLICATE] is invalid when persistent is true.", expected.getMessage()); throw expected; } finally { diff --git a/src/test/java/org/springframework/data/gemfire/ReplicatedRegionFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/ReplicatedRegionFactoryBeanTest.java index fb1332c5..39876410 100644 --- a/src/test/java/org/springframework/data/gemfire/ReplicatedRegionFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/ReplicatedRegionFactoryBeanTest.java @@ -168,7 +168,7 @@ public class ReplicatedRegionFactoryBeanTest { factoryBean.resolveDataPolicy(mockRegionFactory, true, "empty"); } catch (IllegalArgumentException e) { - assertEquals("Data Policy 'EMPTY' is invalid when persistent is true.", e.getMessage()); + assertEquals("Data Policy [EMPTY] is invalid when persistent is true.", e.getMessage()); throw e; } finally { @@ -202,7 +202,7 @@ public class ReplicatedRegionFactoryBeanTest { factoryBean.resolveDataPolicy(mockRegionFactory, true, "REPLICATE"); } catch (IllegalArgumentException e) { - assertEquals("Data Policy 'REPLICATE' is invalid when persistent is true.", e.getMessage()); + assertEquals("Data Policy [REPLICATE] is invalid when persistent is true.", e.getMessage()); throw e; } finally { @@ -228,7 +228,7 @@ public class ReplicatedRegionFactoryBeanTest { factoryBean.resolveDataPolicy(mockRegionFactory, false, "PERSISTENT_REPLICATE"); } catch (IllegalArgumentException e) { - assertEquals("Data Policy 'PERSISTENT_REPLICATE' is invalid when persistent is false.", e.getMessage()); + assertEquals("Data Policy [PERSISTENT_REPLICATE] is invalid when persistent is false.", e.getMessage()); throw e; } finally { @@ -245,5 +245,4 @@ public class ReplicatedRegionFactoryBeanTest { factoryBean.resolveDataPolicy(mockRegionFactory, true, "PERSISTENT_REPLICATE"); verify(mockRegionFactory).setDataPolicy(eq(DataPolicy.PERSISTENT_REPLICATE)); } - } diff --git a/src/test/java/org/springframework/data/gemfire/client/ClientCacheFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/client/ClientCacheFactoryBeanTest.java index 46b5804e..3b07c715 100644 --- a/src/test/java/org/springframework/data/gemfire/client/ClientCacheFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/client/ClientCacheFactoryBeanTest.java @@ -16,7 +16,6 @@ package org.springframework.data.gemfire.client; -import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; @@ -42,6 +41,7 @@ import java.net.InetSocketAddress; import java.util.Arrays; import java.util.Collections; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; @@ -65,15 +65,21 @@ import org.springframework.data.gemfire.util.ArrayUtils; import org.springframework.data.gemfire.util.DistributedSystemUtils; /** - * The ClientCacheFactoryBeanTest class is a test suite of test cases testing the contract and functionality - * of the SDG ClientCacheFactoryBean class. + * Unit tests for {@link ClientCacheFactoryBean} * * @author John Blum - * @see org.mockito.Mockito + * @see java.net.InetSocketAddress + * @see org.junit.Rule * @see org.junit.Test - * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean + * @see org.mockito.Mockito + * @see org.apache.geode.cache.GemFireCache * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.client.ClientCacheFactory + * @see org.apache.geode.cache.client.Pool + * @see org.apache.geode.distributed.DistributedSystem + * @see org.apache.geode.pdx.PdxSerializer + * @see org.springframework.beans.factory.BeanFactory + * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean * @since 1.7.0 */ public class ClientCacheFactoryBeanTest { @@ -81,24 +87,25 @@ public class ClientCacheFactoryBeanTest { @Rule public ExpectedException exception = ExpectedException.none(); - protected Properties createProperties(String key, String value) { + private Properties createProperties(String key, String value) { return addProperty(null, key, value); } - protected Properties addProperty(Properties properties, String key, String value) { - properties = (properties != null ? properties : new Properties()); + @SuppressWarnings("all") + private Properties addProperty(Properties properties, String key, String value) { + properties = Optional.ofNullable(properties).orElseGet(Properties::new); properties.setProperty(key, value); return properties; } - protected ConnectionEndpoint newConnectionEndpoint(String host, int port) { + private ConnectionEndpoint newConnectionEndpoint(String host, int port) { return new ConnectionEndpoint(host, port); } @Test @SuppressWarnings("unchecked") public void getObjectType() { - assertThat((Class) new ClientCacheFactoryBean().getObjectType(), is(equalTo(ClientCache.class))); + assertThat(new ClientCacheFactoryBean().getObjectType(), is(equalTo(ClientCache.class))); } @Test @@ -252,18 +259,21 @@ public class ClientCacheFactoryBeanTest { ClientCacheFactory mockClientCacheFactory = mock(ClientCacheFactory.class); ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean() { - @Override ClientCacheFactory initializePdx(final ClientCacheFactory clientCacheFactory) { + + @Override + ClientCacheFactory initializePdx(ClientCacheFactory clientCacheFactory) { initializePdxCalled.set(true); return clientCacheFactory; } - @Override ClientCacheFactory initializePool(final ClientCacheFactory clientCacheFactory) { + @Override + ClientCacheFactory initializePool(final ClientCacheFactory clientCacheFactory) { initializePoolCalled.set(true); return clientCacheFactory; } }; - assertThat((ClientCacheFactory) clientCacheFactoryBean.prepareFactory(mockClientCacheFactory), + assertThat(clientCacheFactoryBean.prepareFactory(mockClientCacheFactory), is(sameInstance(mockClientCacheFactory))); assertThat(initializePdxCalled.get(), is(true)); assertThat(initializePoolCalled.get(), is(true)); @@ -285,7 +295,7 @@ public class ClientCacheFactoryBeanTest { assertThat(clientCacheFactoryBean.getPdxIgnoreUnreadFields(), is(false)); assertThat(clientCacheFactoryBean.getPdxPersistent(), is(true)); assertThat(clientCacheFactoryBean.getPdxReadSerialized(), is(false)); - assertThat((PdxSerializer) clientCacheFactoryBean.getPdxSerializer(), is(sameInstance(mockPdxSerializer))); + assertThat(clientCacheFactoryBean.getPdxSerializer(), is(sameInstance(mockPdxSerializer))); ClientCacheFactory mockClientCacheFactory = mock(ClientCacheFactory.class); @@ -342,51 +352,19 @@ public class ClientCacheFactoryBeanTest { verifyZeroInteractions(mockClientCacheFactory); } - @Test - public void initializePdxUsingIllegalTypeForPdxSerializer() { - ClientCacheFactory mockClientCacheFactory = mock(ClientCacheFactory.class); - - try { - ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean(); - - Object pdxSerializer = new Object(); - - clientCacheFactoryBean.setPdxSerializer(pdxSerializer); - clientCacheFactoryBean.setPdxReadSerialized(false); - clientCacheFactoryBean.setPdxPersistent(true); - clientCacheFactoryBean.setPdxIgnoreUnreadFields(false); - clientCacheFactoryBean.setPdxDiskStoreName("test"); - - exception.expect(IllegalArgumentException.class); - exception.expectCause(is(nullValue(Throwable.class))); - exception.expectMessage(containsString(String.format( - "[%1$s] of type [java.lang.Object] is not a PdxSerializer", pdxSerializer))); - - assertThat(clientCacheFactoryBean.initializePdx(mockClientCacheFactory), - is(sameInstance(mockClientCacheFactory))); - } - finally { - verify(mockClientCacheFactory, never()).setPdxSerializer(any(PdxSerializer.class)); - verify(mockClientCacheFactory, never()).setPdxDiskStore(any(String.class)); - verify(mockClientCacheFactory, never()).setPdxIgnoreUnreadFields(any(Boolean.class)); - verify(mockClientCacheFactory, never()).setPdxPersistent(any(Boolean.class)); - verify(mockClientCacheFactory, never()).setPdxReadSerialized(any(Boolean.class)); - } - } - @Test public void initializePoolWithPool() { Pool mockPool = mock(Pool.class); when(mockPool.getFreeConnectionTimeout()).thenReturn(10000); - when(mockPool.getIdleTimeout()).thenReturn(120000l); + when(mockPool.getIdleTimeout()).thenReturn(120000L); when(mockPool.getLoadConditioningInterval()).thenReturn(30000); - when(mockPool.getLocators()).thenReturn(Collections.emptyList()); + when(mockPool.getLocators()).thenReturn(Collections.emptyList()); when(mockPool.getMaxConnections()).thenReturn(100); when(mockPool.getMinConnections()).thenReturn(10); when(mockPool.getMultiuserAuthentication()).thenReturn(true); when(mockPool.getPRSingleHopEnabled()).thenReturn(true); - when(mockPool.getPingInterval()).thenReturn(15000l); + when(mockPool.getPingInterval()).thenReturn(15000L); when(mockPool.getReadTimeout()).thenReturn(20000); when(mockPool.getRetryAttempts()).thenReturn(1); when(mockPool.getServerGroup()).thenReturn("TestGroup"); @@ -453,13 +431,13 @@ public class ClientCacheFactoryBeanTest { verify(mockPool, times(1)).getSubscriptionRedundancy(); verify(mockPool, times(1)).getThreadLocalConnections(); verify(mockClientCacheFactory, times(1)).setPoolFreeConnectionTimeout(eq(10000)); - verify(mockClientCacheFactory, times(1)).setPoolIdleTimeout(eq(120000l)); + verify(mockClientCacheFactory, times(1)).setPoolIdleTimeout(eq(120000L)); verify(mockClientCacheFactory, times(1)).setPoolLoadConditioningInterval(eq(30000)); verify(mockClientCacheFactory, times(1)).setPoolMaxConnections(eq(100)); verify(mockClientCacheFactory, times(1)).setPoolMinConnections(eq(10)); verify(mockClientCacheFactory, times(1)).setPoolMultiuserAuthentication(eq(true)); verify(mockClientCacheFactory, times(1)).setPoolPRSingleHopEnabled(eq(true)); - verify(mockClientCacheFactory, times(1)).setPoolPingInterval(eq(15000l)); + verify(mockClientCacheFactory, times(1)).setPoolPingInterval(eq(15000L)); verify(mockClientCacheFactory, times(1)).setPoolReadTimeout(eq(20000)); verify(mockClientCacheFactory, times(1)).setPoolRetryAttempts(eq(1)); verify(mockClientCacheFactory, times(1)).setPoolServerGroup(eq("TestGroup")); @@ -482,12 +460,12 @@ public class ClientCacheFactoryBeanTest { ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean(); clientCacheFactoryBean.setFreeConnectionTimeout(5000); - clientCacheFactoryBean.setIdleTimeout(300000l); + clientCacheFactoryBean.setIdleTimeout(300000L); clientCacheFactoryBean.setLoadConditioningInterval(120000); clientCacheFactoryBean.setMaxConnections(99); clientCacheFactoryBean.setMinConnections(9); clientCacheFactoryBean.setMultiUserAuthentication(true); - clientCacheFactoryBean.setPingInterval(15000l); + clientCacheFactoryBean.setPingInterval(15000L); clientCacheFactoryBean.setPool(mockPool); clientCacheFactoryBean.setPrSingleHopEnabled(true); clientCacheFactoryBean.setReadTimeout(20000); @@ -504,13 +482,13 @@ public class ClientCacheFactoryBeanTest { newConnectionEndpoint("skullbox", 10334)); assertThat(clientCacheFactoryBean.getFreeConnectionTimeout(), is(equalTo(5000))); - assertThat(clientCacheFactoryBean.getIdleTimeout(), is(equalTo(300000l))); + assertThat(clientCacheFactoryBean.getIdleTimeout(), is(equalTo(300000L))); assertThat(clientCacheFactoryBean.getLoadConditioningInterval(), is(equalTo(120000))); assertThat(clientCacheFactoryBean.getLocators().size(), is(equalTo(2))); assertThat(clientCacheFactoryBean.getMaxConnections(), is(equalTo(99))); assertThat(clientCacheFactoryBean.getMinConnections(), is(equalTo(9))); assertThat(clientCacheFactoryBean.getMultiUserAuthentication(), is(equalTo(true))); - assertThat(clientCacheFactoryBean.getPingInterval(), is(equalTo(15000l))); + assertThat(clientCacheFactoryBean.getPingInterval(), is(equalTo(15000L))); assertThat(clientCacheFactoryBean.getPool(), is(sameInstance(mockPool))); assertThat(clientCacheFactoryBean.getPoolName(), is(nullValue())); assertThat(clientCacheFactoryBean.getPrSingleHopEnabled(), is(equalTo(true))); @@ -533,12 +511,12 @@ public class ClientCacheFactoryBeanTest { verifyZeroInteractions(mockPool); verify(mockClientCacheFactory, times(1)).setPoolFreeConnectionTimeout(eq(5000)); - verify(mockClientCacheFactory, times(1)).setPoolIdleTimeout(eq(300000l)); + verify(mockClientCacheFactory, times(1)).setPoolIdleTimeout(eq(300000L)); verify(mockClientCacheFactory, times(1)).setPoolLoadConditioningInterval(eq(120000)); verify(mockClientCacheFactory, times(1)).setPoolMaxConnections(eq(99)); verify(mockClientCacheFactory, times(1)).setPoolMinConnections(eq(9)); verify(mockClientCacheFactory, times(1)).setPoolMultiuserAuthentication(eq(true)); - verify(mockClientCacheFactory, times(1)).setPoolPingInterval(eq(15000l)); + verify(mockClientCacheFactory, times(1)).setPoolPingInterval(eq(15000L)); verify(mockClientCacheFactory, times(1)).setPoolPRSingleHopEnabled(eq(true)); verify(mockClientCacheFactory, times(1)).setPoolReadTimeout(eq(20000)); verify(mockClientCacheFactory, times(1)).setPoolRetryAttempts(eq(2)); @@ -560,13 +538,13 @@ public class ClientCacheFactoryBeanTest { Pool mockPool = mock(Pool.class); when(mockPool.getFreeConnectionTimeout()).thenReturn(5000); - when(mockPool.getIdleTimeout()).thenReturn(120000l); + when(mockPool.getIdleTimeout()).thenReturn(120000L); when(mockPool.getLoadConditioningInterval()).thenReturn(300000); - when(mockPool.getLocators()).thenReturn(Collections.emptyList()); + when(mockPool.getLocators()).thenReturn(Collections.emptyList()); when(mockPool.getMaxConnections()).thenReturn(200); when(mockPool.getMinConnections()).thenReturn(10); when(mockPool.getMultiuserAuthentication()).thenReturn(false); - when(mockPool.getPingInterval()).thenReturn(15000l); + when(mockPool.getPingInterval()).thenReturn(15000L); when(mockPool.getPRSingleHopEnabled()).thenReturn(false); when(mockPool.getReadTimeout()).thenReturn(30000); when(mockPool.getRetryAttempts()).thenReturn(1); @@ -582,7 +560,7 @@ public class ClientCacheFactoryBeanTest { ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean(); - clientCacheFactoryBean.setIdleTimeout(180000l); + clientCacheFactoryBean.setIdleTimeout(180000L); clientCacheFactoryBean.setMaxConnections(500); clientCacheFactoryBean.setMinConnections(50); clientCacheFactoryBean.setMultiUserAuthentication(true); @@ -598,7 +576,7 @@ public class ClientCacheFactoryBeanTest { clientCacheFactoryBean.addLocators(newConnectionEndpoint("localhost", 11235)); assertThat(clientCacheFactoryBean.getFreeConnectionTimeout(), is(nullValue())); - assertThat(clientCacheFactoryBean.getIdleTimeout(), is(equalTo(180000l))); + assertThat(clientCacheFactoryBean.getIdleTimeout(), is(equalTo(180000L))); assertThat(clientCacheFactoryBean.getLoadConditioningInterval(), is(nullValue())); assertThat(clientCacheFactoryBean.getLocators().size(), is(equalTo(1))); assertThat(clientCacheFactoryBean.getMaxConnections(), is(equalTo(500))); @@ -646,12 +624,12 @@ public class ClientCacheFactoryBeanTest { verify(mockPool, never()).getSubscriptionRedundancy(); verify(mockPool, never()).getThreadLocalConnections(); verify(mockClientCacheFactory, times(1)).setPoolFreeConnectionTimeout(eq(5000)); - verify(mockClientCacheFactory, times(1)).setPoolIdleTimeout(eq(180000l)); + verify(mockClientCacheFactory, times(1)).setPoolIdleTimeout(eq(180000L)); verify(mockClientCacheFactory, times(1)).setPoolLoadConditioningInterval(eq(300000)); verify(mockClientCacheFactory, times(1)).setPoolMaxConnections(eq(500)); verify(mockClientCacheFactory, times(1)).setPoolMinConnections(eq(50)); verify(mockClientCacheFactory, times(1)).setPoolMultiuserAuthentication(eq(true)); - verify(mockClientCacheFactory, times(1)).setPoolPingInterval(eq(15000l)); + verify(mockClientCacheFactory, times(1)).setPoolPingInterval(eq(15000L)); verify(mockClientCacheFactory, times(1)).setPoolPRSingleHopEnabled(eq(true)); verify(mockClientCacheFactory, times(1)).setPoolReadTimeout(eq(30000)); verify(mockClientCacheFactory, times(1)).setPoolRetryAttempts(eq(1)); @@ -724,7 +702,7 @@ public class ClientCacheFactoryBeanTest { public void initializePoolWithPoolServer() { Pool mockPool = mock(Pool.class); - when(mockPool.getLocators()).thenReturn(Collections.emptyList()); + when(mockPool.getLocators()).thenReturn(Collections.emptyList()); when(mockPool.getServers()).thenReturn(Collections.singletonList(new InetSocketAddress("boombox", 41414))); ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean(); @@ -751,7 +729,7 @@ public class ClientCacheFactoryBeanTest { Pool mockPool = mock(Pool.class); when(mockPool.getLocators()).thenReturn(Collections.singletonList(new InetSocketAddress("skullbox", 21668))); - when(mockPool.getServers()).thenReturn(Collections.emptyList()); + when(mockPool.getServers()).thenReturn(Collections.emptyList()); ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean(); @@ -803,8 +781,7 @@ public class ClientCacheFactoryBeanTest { ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean(); - assertThat((ClientCache) clientCacheFactoryBean.createCache(mockClientCacheFactory), - is(sameInstance(mockClientCache))); + assertThat(clientCacheFactoryBean.createCache(mockClientCacheFactory), is(sameInstance(mockClientCache))); verify(mockClientCacheFactory, times(1)).create(); verifyZeroInteractions(mockClientCache); @@ -878,7 +855,7 @@ public class ClientCacheFactoryBeanTest { clientCacheFactoryBean.setBeanFactory(mockBeanFactory); clientCacheFactoryBean.setPoolName("TestPool"); - assertThat((ListableBeanFactory) clientCacheFactoryBean.getBeanFactory(), is(equalTo(mockBeanFactory))); + assertThat(clientCacheFactoryBean.getBeanFactory(), is(equalTo(mockBeanFactory))); assertThat(clientCacheFactoryBean.getPool(), is(nullValue())); assertThat(clientCacheFactoryBean.getPoolName(), is(equalTo("TestPool"))); assertThat(clientCacheFactoryBean.resolvePool(), is(equalTo(mockPool))); @@ -903,7 +880,7 @@ public class ClientCacheFactoryBeanTest { clientCacheFactoryBean.setBeanFactory(mockBeanFactory); - assertThat((ListableBeanFactory) clientCacheFactoryBean.getBeanFactory(), is(equalTo(mockBeanFactory))); + assertThat(clientCacheFactoryBean.getBeanFactory(), is(equalTo(mockBeanFactory))); assertThat(clientCacheFactoryBean.getPool(), is(nullValue())); assertThat(clientCacheFactoryBean.getPoolName(), is(nullValue())); assertThat(clientCacheFactoryBean.resolvePool(), is(equalTo(mockPool))); @@ -925,7 +902,7 @@ public class ClientCacheFactoryBeanTest { clientCacheFactoryBean.setBeanFactory(mockBeanFactory); - assertThat((ListableBeanFactory) clientCacheFactoryBean.getBeanFactory(), is(equalTo(mockBeanFactory))); + assertThat(clientCacheFactoryBean.getBeanFactory(), is(equalTo(mockBeanFactory))); assertThat(clientCacheFactoryBean.getPool(), is(nullValue())); assertThat(clientCacheFactoryBean.getPoolName(), is(nullValue())); assertThat(clientCacheFactoryBean.resolvePool(), is(nullValue())); @@ -949,7 +926,7 @@ public class ClientCacheFactoryBeanTest { clientCacheFactoryBean.setBeanFactory(mockBeanFactory); clientCacheFactoryBean.setPoolName("TestPool"); - assertThat((ListableBeanFactory) clientCacheFactoryBean.getBeanFactory(), is(equalTo(mockBeanFactory))); + assertThat(clientCacheFactoryBean.getBeanFactory(), is(equalTo(mockBeanFactory))); assertThat(clientCacheFactoryBean.getPool(), is(nullValue())); assertThat(clientCacheFactoryBean.getPoolName(), is(equalTo("TestPool"))); assertThat(clientCacheFactoryBean.resolvePool(), is(nullValue())); @@ -1176,12 +1153,12 @@ public class ClientCacheFactoryBeanTest { assertThat(clientCacheFactoryBean.getThreadLocalConnections(), is(nullValue())); clientCacheFactoryBean.setFreeConnectionTimeout(5000); - clientCacheFactoryBean.setIdleTimeout(120000l); + clientCacheFactoryBean.setIdleTimeout(120000L); clientCacheFactoryBean.setLoadConditioningInterval(300000); clientCacheFactoryBean.setMaxConnections(500); clientCacheFactoryBean.setMinConnections(50); clientCacheFactoryBean.setMultiUserAuthentication(true); - clientCacheFactoryBean.setPingInterval(15000l); + clientCacheFactoryBean.setPingInterval(15000L); clientCacheFactoryBean.setPrSingleHopEnabled(true); clientCacheFactoryBean.setReadTimeout(30000); clientCacheFactoryBean.setRetryAttempts(1); @@ -1195,12 +1172,12 @@ public class ClientCacheFactoryBeanTest { clientCacheFactoryBean.setThreadLocalConnections(false); assertThat(clientCacheFactoryBean.getFreeConnectionTimeout(), is(equalTo(5000))); - assertThat(clientCacheFactoryBean.getIdleTimeout(), is(equalTo(120000l))); + assertThat(clientCacheFactoryBean.getIdleTimeout(), is(equalTo(120000L))); assertThat(clientCacheFactoryBean.getLoadConditioningInterval(), is(equalTo(300000))); assertThat(clientCacheFactoryBean.getMaxConnections(), is(equalTo(500))); assertThat(clientCacheFactoryBean.getMinConnections(), is(equalTo(50))); assertThat(clientCacheFactoryBean.getMultiUserAuthentication(), is(equalTo(true))); - assertThat(clientCacheFactoryBean.getPingInterval(), is(equalTo(15000l))); + assertThat(clientCacheFactoryBean.getPingInterval(), is(equalTo(15000L))); assertThat(clientCacheFactoryBean.getPrSingleHopEnabled(), is(equalTo(true))); assertThat(clientCacheFactoryBean.getReadTimeout(), is(equalTo(30000))); assertThat(clientCacheFactoryBean.getRetryAttempts(), is(equalTo(1))); @@ -1233,7 +1210,7 @@ public class ClientCacheFactoryBeanTest { assertThat(clientCacheFactoryBean.getReadyForEvents(), is(false)); } - protected ClientCache mockClientCache(String durableClientId) { + private ClientCache mockClientCache(String durableClientId) { ClientCache mockClientCache = mock(ClientCache.class); DistributedSystem mockDistributedSystem = mock(DistributedSystem.class); @@ -1363,7 +1340,7 @@ public class ClientCacheFactoryBeanTest { assertThat(clientCacheFactoryBean.getLocators().size(), is(equalTo(1))); assertThat(clientCacheFactoryBean.getLocators().findOne("localhost"), is(equalTo(localhost))); - clientCacheFactoryBean.setLocators(Collections.emptyList()); + clientCacheFactoryBean.setLocators(Collections.emptyList()); assertThat(clientCacheFactoryBean.getLocators(), is(notNullValue())); assertThat(clientCacheFactoryBean.getLocators().isEmpty(), is(true)); @@ -1398,7 +1375,7 @@ public class ClientCacheFactoryBeanTest { assertThat(clientCacheFactoryBean.getServers().size(), is(equalTo(1))); assertThat(clientCacheFactoryBean.getServers().findOne("localhost"), is(equalTo(localhost))); - clientCacheFactoryBean.setServers(Collections.emptyList()); + clientCacheFactoryBean.setServers(Collections.emptyList()); assertThat(clientCacheFactoryBean.getServers(), is(notNullValue())); assertThat(clientCacheFactoryBean.getServers().isEmpty(), is(true)); diff --git a/src/test/java/org/springframework/data/gemfire/client/ClientRegionFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/client/ClientRegionFactoryBeanTest.java index d7791def..9412d78c 100644 --- a/src/test/java/org/springframework/data/gemfire/client/ClientRegionFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/client/ClientRegionFactoryBeanTest.java @@ -140,7 +140,7 @@ public class ClientRegionFactoryBeanTest { factoryBean.setSnapshot(mockSnapshot); factoryBean.setShortcut(null); - Region actualRegion = factoryBean.lookupRegion(mockClientCache, testRegionName); + Region actualRegion = factoryBean.createRegion(mockClientCache, testRegionName); assertSame(mockRegion, actualRegion); @@ -190,7 +190,7 @@ public class ClientRegionFactoryBeanTest { factoryBean.setPoolName("TestPool"); factoryBean.setShortcut(null); - Region actualRegion = factoryBean.lookupRegion(mockClientCache, "TestRegion"); + Region actualRegion = factoryBean.createRegion(mockClientCache, "TestRegion"); assertSame(mockRegion, actualRegion); @@ -218,7 +218,7 @@ public class ClientRegionFactoryBeanTest { factoryBean.setBeanFactory(mockBeanFactory); factoryBean.setShortcut(ClientRegionShortcut.CACHING_PROXY); - Region actualRegion = factoryBean.lookupRegion(mockClientCache, "TestRegion"); + Region actualRegion = factoryBean.createRegion(mockClientCache, "TestRegion"); assertSame(mockRegion, actualRegion); @@ -246,7 +246,7 @@ public class ClientRegionFactoryBeanTest { factoryBean.setParent(mockRegion); factoryBean.setShortcut(ClientRegionShortcut.PROXY); - Region actualRegion = factoryBean.lookupRegion(mockClientCache, "TestSubRegion"); + Region actualRegion = factoryBean.createRegion(mockClientCache, "TestSubRegion"); assertSame(mockSubRegion, actualRegion); @@ -271,7 +271,7 @@ public class ClientRegionFactoryBeanTest { factoryBean.setBeanFactory(mockBeanFactory); factoryBean.setShortcut(ClientRegionShortcut.LOCAL_HEAP_LRU); - Region actualRegion = factoryBean.lookupRegion(mockClientCache, "TestRegion"); + Region actualRegion = factoryBean.createRegion(mockClientCache, "TestRegion"); assertSame(mockRegion, actualRegion); @@ -295,7 +295,7 @@ public class ClientRegionFactoryBeanTest { factoryBean.setDataPolicyName("INVALID"); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'INVALID' is invalid.", expected.getMessage()); + assertEquals("Data Policy [INVALID] is not valid", expected.getMessage()); throw expected; } finally { @@ -440,7 +440,7 @@ public class ClientRegionFactoryBeanTest { factoryBean.resolveClientRegionShortcut(); } catch (IllegalArgumentException expected) { - assertEquals("Client Region Shortcut 'CACHING_PROXY' is invalid when persistent is true", + assertEquals("Client Region Shortcut [CACHING_PROXY] is not valid when persistent is true", expected.getMessage()); throw expected; } @@ -467,7 +467,7 @@ public class ClientRegionFactoryBeanTest { factoryBean.resolveClientRegionShortcut(); } catch (IllegalArgumentException expected) { - assertEquals("Client Region Shortcut 'LOCAL_PERSISTENT' is invalid when persistent is false", + assertEquals("Client Region Shortcut [LOCAL_PERSISTENT] is not valid when persistent is false", expected.getMessage()); throw expected; } @@ -514,7 +514,7 @@ public class ClientRegionFactoryBeanTest { factoryBean.resolveClientRegionShortcut(); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'NORMAL' is invalid when persistent is true", expected.getMessage()); + assertEquals("Data Policy [NORMAL] is not valid when persistent is true", expected.getMessage()); throw expected; } } @@ -540,7 +540,7 @@ public class ClientRegionFactoryBeanTest { factoryBean.resolveClientRegionShortcut(); } catch (IllegalArgumentException expected) { - assertEquals("Data Policy 'PERSISTENT_REPLICATE' is invalid when persistent is false", expected.getMessage()); + assertEquals("Data Policy [PERSISTENT_REPLICATE] is not valid when persistent is false", expected.getMessage()); throw expected; } } diff --git a/src/test/java/org/springframework/data/gemfire/client/PoolFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/client/PoolFactoryBeanTest.java index 26f5ae7d..c32fd94a 100644 --- a/src/test/java/org/springframework/data/gemfire/client/PoolFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/client/PoolFactoryBeanTest.java @@ -52,19 +52,15 @@ import org.springframework.data.gemfire.util.ArrayUtils; import org.springframework.data.util.ReflectionUtils; /** - * The PoolFactoryBeanTest class is a test suite of test cases testing the contract and functionality - * of the PoolFactoryBean class. + * Unit tests for {@link PoolFactoryBean}. * * @author John Blum * @see org.junit.Rule * @see org.junit.Test * @see org.junit.rules.ExpectedException * @see org.mockito.Mockito - * @see org.springframework.data.gemfire.client.PoolFactoryBean - * @see org.springframework.data.gemfire.support.ConnectionEndpoint - * @see org.apache.geode.cache.GemFireCache * @see org.apache.geode.cache.client.Pool - * @see org.apache.geode.cache.client.PoolFactory + * @see org.springframework.data.gemfire.client.PoolFactoryBean * @since 1.7.0 */ public class PoolFactoryBeanTest { @@ -164,7 +160,7 @@ public class PoolFactoryBeanTest { exception.expect(IllegalArgumentException.class); exception.expectCause(is(nullValue(Throwable.class))); - exception.expectMessage("Pool 'name' is required"); + exception.expectMessage("Pool name is required"); poolFactoryBean.afterPropertiesSet(); } diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfigurationUnitTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfigurationUnitTests.java index 1b9308fb..d843fe74 100644 --- a/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/AbstractCacheConfigurationUnitTests.java @@ -18,6 +18,7 @@ package org.springframework.data.gemfire.config.annotation; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; @@ -53,8 +54,10 @@ import org.springframework.util.MethodInvoker; * * @author John Blum * @see org.junit.Test + * @see org.junit.runner.RunWith * @see org.mockito.Mock * @see org.mockito.Mockito + * @see org.mockito.Spy * @see org.mockito.junit.MockitoJUnitRunner * @see org.springframework.data.gemfire.config.annotation.AbstractCacheConfiguration * @since 1.9.0 @@ -174,7 +177,7 @@ public class AbstractCacheConfigurationUnitTests { MappingPdxSerializer mockPdxSerializer = mock(MappingPdxSerializer.class); when(mockBeanFactory.containsBean(anyString())).thenReturn(false); - doReturn(mockPdxSerializer).when(cacheConfigurationSpy).newPdxSerializer(); + doReturn(mockPdxSerializer).when(cacheConfigurationSpy).newPdxSerializer(any(BeanFactory.class)); cacheConfigurationSpy.setBeanFactory(mockBeanFactory); @@ -184,7 +187,7 @@ public class AbstractCacheConfigurationUnitTests { verify(mockBeanFactory, times(1)).containsBean(eq("TestPdxSerializer")); verify(mockBeanFactory, never()).getBean(anyString(), eq(PdxSerializer.class)); - verify(cacheConfigurationSpy, times(1)).newPdxSerializer(); + verify(cacheConfigurationSpy, times(1)).newPdxSerializer(eq(mockBeanFactory)); } @Test diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/CacheServerConfigurerIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/CacheServerConfigurerIntegrationTests.java new file mode 100644 index 00000000..a0b57cef --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/CacheServerConfigurerIntegrationTests.java @@ -0,0 +1,130 @@ +/* + * Copyright 2017 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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.gemfire.server.CacheServerFactoryBean; +import org.springframework.data.gemfire.test.GemfireTestBeanPostProcessor; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for {@link CacheServerConfigurer}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.cache.server.CacheServer + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.data.gemfire.config.annotation.CacheServerConfigurer + * @see org.springframework.data.gemfire.config.annotation.EnableCacheServer + * @see org.springframework.data.gemfire.config.annotation.EnableCacheServers + * @see org.springframework.data.gemfire.server.CacheServerFactoryBean + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 1.1.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@SuppressWarnings("unused") +public class CacheServerConfigurerIntegrationTests { + + @Autowired + @Qualifier("configurerOne") + private TestCacheServerConfigurer configurerOne; + + @Autowired + @Qualifier("configurerTwo") + private TestCacheServerConfigurer configurerTwo; + + private void assertCacheServerConfigurerCalled(TestCacheServerConfigurer configurer, + String... cacheServerBeanNames) { + + assertThat(configurer).isNotNull(); + assertThat(configurer).hasSize(cacheServerBeanNames.length); + assertThat(configurer).contains(cacheServerBeanNames); + } + + @Test + public void cacheServerConfigurerOneCalledSuccessfully() { + assertCacheServerConfigurerCalled(this.configurerOne, + "gemfireCacheServer", "marsServer", "saturnServer", "venusServer"); + } + + @Test + public void cacheServerConfigurerTwoCalledSuccessfully() { + assertCacheServerConfigurerCalled(this.configurerTwo, + "gemfireCacheServer", "marsServer", "saturnServer", "venusServer"); + } + + @Configuration + @CacheServerApplication + @EnableCacheServers(servers = { + @EnableCacheServer(name = "marsServer"), + @EnableCacheServer(name = "saturnServer"), + @EnableCacheServer(name = "venusServer"), + }) + static class TestConfiguration { + + @Bean + GemfireTestBeanPostProcessor testBeanPostProcessor() { + return new GemfireTestBeanPostProcessor(); + } + + @Bean + TestCacheServerConfigurer configurerOne() { + return new TestCacheServerConfigurer(); + } + + @Bean + TestCacheServerConfigurer configurerTwo() { + return new TestCacheServerConfigurer(); + } + + @Bean + Object nonRelevantBean() { + return "test"; + } + } + + static class TestCacheServerConfigurer implements CacheServerConfigurer, Iterable { + + private final Set beanNames = new HashSet<>(); + + @Override + public void configure(String beanName, CacheServerFactoryBean bean) { + this.beanNames.add(beanName); + } + + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.beanNames).iterator(); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfigurerIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfigurerIntegrationTests.java new file mode 100644 index 00000000..4266fe48 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/ClientCacheConfigurerIntegrationTests.java @@ -0,0 +1,127 @@ +/* + * Copyright 2017 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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.geode.cache.client.ClientCache; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.client.ClientCacheFactoryBean; +import org.springframework.data.gemfire.test.GemfireTestBeanPostProcessor; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for {@link ClientCacheConfigurer}. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.apache.geode.cache.client.ClientCache + * @see org.springframework.data.gemfire.client.ClientCacheFactoryBean + * @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication + * @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer + * @see org.springframework.data.gemfire.test.GemfireTestBeanPostProcessor + * @see org.springframework.test.context.junit4.SpringRunner + * @since 1.1.0 + */ +@RunWith(SpringRunner.class) +@SuppressWarnings("unused") +public class ClientCacheConfigurerIntegrationTests { + + @Autowired + private ClientCache clientCache; + + @Autowired + @Qualifier("testClientCacheConfigurerOne") + private TestClientCacheConfigurer configurerOne; + + @Autowired + @Qualifier("testClientCacheConfigurerTwo") + private TestClientCacheConfigurer configurerTwo; + + @Before + public void setup() { + assertThat(this.clientCache).isNotNull(); + } + + private void assertClientCacheConfigurerInvokedSuccessfully(TestClientCacheConfigurer clientCacheConfigurer, + String... beanNames) { + + assertThat(clientCacheConfigurer).isNotNull(); + assertThat(clientCacheConfigurer).hasSize(beanNames.length); + assertThat(clientCacheConfigurer).contains(beanNames); + } + + @Test + public void clientCacheConfigurerOneCalledSuccessfully() { + assertClientCacheConfigurerInvokedSuccessfully(this.configurerOne, "gemfireCache"); + } + + @Test + public void clientCacheConfigurerTwoCalledSuccessfully() { + assertClientCacheConfigurerInvokedSuccessfully(this.configurerTwo, "gemfireCache"); + } + + @ClientCacheApplication + static class TestConfiguration { + + @Bean + GemfireTestBeanPostProcessor testBeanPostProcessor() { + return new GemfireTestBeanPostProcessor(); + } + + @Bean + TestClientCacheConfigurer testClientCacheConfigurerOne() { + return new TestClientCacheConfigurer(); + } + + @Bean + TestClientCacheConfigurer testClientCacheConfigurerTwo() { + return new TestClientCacheConfigurer(); + } + + @Bean + String nonRelevantBean() { + return "test"; + } + } + + static final class TestClientCacheConfigurer implements ClientCacheConfigurer, Iterable { + + private Set beanNames = new HashSet<>(); + + @Override + public void configure(String beanName, ClientCacheFactoryBean bean) { + this.beanNames.add(beanName); + } + + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.beanNames).iterator(); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfigurerIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfigurerIntegrationTests.java new file mode 100644 index 00000000..f9c60e22 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/DiskStoreConfigurerIntegrationTests.java @@ -0,0 +1,127 @@ +/* + * Copyright 2017 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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.DiskStoreFactoryBean; +import org.springframework.data.gemfire.test.GemfireTestBeanPostProcessor; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for {@link DiskStoreConfigurer}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.cache.DiskStore + * @see org.apache.geode.cache.DiskStoreFactory + * @see org.apache.geode.cache.GemFireCache + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.data.gemfire.DiskStoreFactoryBean + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfiguration + * @see org.springframework.data.gemfire.config.annotation.DiskStoreConfigurer + * @see org.springframework.data.gemfire.config.annotation.EnableDiskStore + * @see org.springframework.data.gemfire.config.annotation.EnableDiskStores + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 1.1.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@SuppressWarnings("unused") +public class DiskStoreConfigurerIntegrationTests { + + @Autowired + @Qualifier("configurerOne") + private TestDiskStoreConfigurer configurerOne; + + @Autowired + @Qualifier("configurerTwo") + private TestDiskStoreConfigurer configurerTwo; + + private void assertDiskStoreConfigurerCalled(TestDiskStoreConfigurer configurer, String... beanNames) { + assertThat(configurer).isNotNull(); + assertThat(configurer).hasSize(beanNames.length); + assertThat(configurer).contains(beanNames); + } + + @Test + public void diskStoreConfigurerOneCalledSuccessfully() { + assertDiskStoreConfigurerCalled(this.configurerOne, "cd", "floppy", "tape"); + } + + @Test + public void diskStoreConfigurerTwoCalledSuccessfully() { + assertDiskStoreConfigurerCalled(this.configurerTwo, "cd", "floppy", "tape"); + } + + @PeerCacheApplication + @EnableDiskStores(diskStores = { + @EnableDiskStore(name = "cd"), + @EnableDiskStore(name = "floppy"), + @EnableDiskStore(name = "tape"), + }) + static class TestConfiguration { + + @Bean + GemfireTestBeanPostProcessor testBeanPostProcessor() { + return new GemfireTestBeanPostProcessor(); + } + + @Bean + TestDiskStoreConfigurer configurerOne() { + return new TestDiskStoreConfigurer(); + } + + @Bean + TestDiskStoreConfigurer configurerTwo() { + return new TestDiskStoreConfigurer(); + } + + @Bean + Object nonRelevantBean() { + return "test"; + } + } + + static class TestDiskStoreConfigurer implements DiskStoreConfigurer, Iterable { + + private final Set beanNames = new HashSet<>(); + + @Override + public void configure(String beanName, DiskStoreFactoryBean bean) { + this.beanNames.add(beanName); + } + + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.beanNames).iterator(); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/IndexConfigurerIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/IndexConfigurerIntegrationTests.java new file mode 100644 index 00000000..bed5d5ab --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/IndexConfigurerIntegrationTests.java @@ -0,0 +1,211 @@ +/* + * Copyright 2017 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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Optional; +import java.util.Set; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.lucene.LuceneIndex; +import org.apache.geode.cache.lucene.LuceneService; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.data.gemfire.IndexFactoryBean; +import org.springframework.data.gemfire.config.annotation.test.entities.CollocatedPartitionRegionEntity; +import org.springframework.data.gemfire.config.annotation.test.entities.NonEntity; +import org.springframework.data.gemfire.mapping.annotation.ClientRegion; +import org.springframework.data.gemfire.mapping.annotation.LocalRegion; +import org.springframework.data.gemfire.mapping.annotation.ReplicateRegion; +import org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean; +import org.springframework.data.gemfire.test.GemfireTestBeanPostProcessor; + +/** + * Integration tests for {@link IndexConfigurer}. + * + * @author John Blum + * @see org.junit.Test + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.lucene.LuceneIndex + * @see org.apache.geode.cache.query.Index + * @see org.springframework.data.gemfire.IndexFactoryBean + * @see org.springframework.data.gemfire.config.annotation.IndexConfigurer + * @see org.springframework.data.gemfire.search.lucene.LuceneIndexFactoryBean + * @since 1.0.0 + */ +public class IndexConfigurerIntegrationTests { + + private static ConfigurableApplicationContext applicationContext; + + @BeforeClass + public static void setup() { + + applicationContext = newApplicationContext(TestConfiguration.class); + + assertThat(applicationContext).isNotNull(); + assertThat(applicationContext.containsBean("CustomersFirstNameFunctionalIdx")).isTrue(); + assertThat(applicationContext.containsBean("CustomersIdKeyIdx")).isTrue(); + assertThat(applicationContext.containsBean("GenericRegionEntityIdKeyIdx")).isTrue(); + assertThat(applicationContext.containsBean("LastNameIdx")).isTrue(); + assertThat(applicationContext.containsBean("luceneIndex")).isTrue(); + assertThat(applicationContext.containsBean("oqlIndex")).isTrue(); + assertThat(applicationContext.containsBean("TitleLuceneIdx")).isTrue(); + } + + @AfterClass + public static void tearDown() { + Optional.ofNullable(applicationContext).ifPresent(ConfigurableApplicationContext::close); + } + + /* (non-Javadoc) */ + private static ConfigurableApplicationContext newApplicationContext(Class... annotatedClasses) { + ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(annotatedClasses); + applicationContext.registerShutdownHook(); + return applicationContext; + } + + /* (non-Javadoc) */ + private void assertIndexConfigurerInvocations(TestIndexConfigurer indexConfigurer, String... indexBeanNames) { + assertThat(indexConfigurer).isNotNull(); + assertThat(indexConfigurer).contains(indexBeanNames); + assertThat(indexConfigurer).hasSize(indexBeanNames.length); + } + + @Test + public void indexConfigurerOneCalledSuccessfully() { + + assertIndexConfigurerInvocations( + applicationContext.getBean("testIndexConfigurerOne", TestIndexConfigurer.class), + "CustomersFirstNameFunctionalIdx", "CustomersIdKeyIdx", "GenericRegionEntityIdKeyIdx", + "LastNameIdx", "TitleLuceneIdx"); + } + + @Test + public void indexConfigurerTwoCalledSuccessfully() { + + assertIndexConfigurerInvocations( + applicationContext.getBean("testIndexConfigurerTwo", TestIndexConfigurer.class), + "CustomersFirstNameFunctionalIdx", "CustomersIdKeyIdx", "GenericRegionEntityIdKeyIdx", + "LastNameIdx", "TitleLuceneIdx"); + } + + @PeerCacheApplication + @EnableIndexing + @SuppressWarnings("unused") + @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class, + excludeFilters = { + @ComponentScan.Filter(type = FilterType.ANNOTATION, + classes = { ClientRegion.class, LocalRegion.class, ReplicateRegion.class }), + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, + classes = CollocatedPartitionRegionEntity.class) + } + ) + static class TestConfiguration { + + @Bean + GemfireTestBeanPostProcessor testBeanPostProcessor() { + return new GemfireTestBeanPostProcessor(); + } + + @Bean + BeanPostProcessor indexFactoryBeanReplacingBeanPostProcessor() { + + return new BeanPostProcessor() { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + + if (bean instanceof LuceneIndexFactoryBean) { + LuceneIndexFactoryBean luceneIndexFactoryBean = (LuceneIndexFactoryBean) bean; + LuceneService mockLuceneService = mock(LuceneService.class); + LuceneIndex mockLuceneIndex = mock(LuceneIndex.class); + + luceneIndexFactoryBean.setLuceneIndex(mockLuceneIndex); + luceneIndexFactoryBean.setLuceneService(mockLuceneService); + luceneIndexFactoryBean.setRegionPath("/Test"); + } + + return bean; + } + }; + } + @Bean + LuceneIndexFactoryBean luceneIndex() { + return new LuceneIndexFactoryBean(); + } + + @Bean + IndexFactoryBean oqlIndex(GemFireCache cache) { + + IndexFactoryBean indexFactory = new IndexFactoryBean(); + + indexFactory.setCache(cache); + indexFactory.setExpression("*"); + indexFactory.setFrom("/Test"); + + return indexFactory; + } + + @Bean + TestIndexConfigurer testIndexConfigurerOne() { + return new TestIndexConfigurer(); + } + + @Bean + TestIndexConfigurer testIndexConfigurerTwo() { + return new TestIndexConfigurer(); + } + + @Bean + String nonRelevantBean() { + return "test"; + } + } + + private static class TestIndexConfigurer implements IndexConfigurer, Iterable { + + private final Set beanNames = new HashSet<>(); + + @Override + public void configure(String beanName, IndexFactoryBean bean) { + this.beanNames.add(beanName); + } + + @Override + public void configure(String beanName, LuceneIndexFactoryBean bean) { + this.beanNames.add(beanName); + } + + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.beanNames).iterator(); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/PeerCacheConfigurerIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/PeerCacheConfigurerIntegrationTests.java new file mode 100644 index 00000000..43f60f51 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/PeerCacheConfigurerIntegrationTests.java @@ -0,0 +1,127 @@ +/* + * Copyright 2017 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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.geode.cache.Cache; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.data.gemfire.test.GemfireTestBeanPostProcessor; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for {@link PeerCacheConfigurer}. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.apache.geode.cache.Cache + * @see org.springframework.data.gemfire.CacheFactoryBean + * @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication + * @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer + * @see org.springframework.data.gemfire.test.GemfireTestBeanPostProcessor + * @see org.springframework.test.context.junit4.SpringRunner + * @since 1.0.0 + */ +@RunWith(SpringRunner.class) +@SuppressWarnings("unused") +public class PeerCacheConfigurerIntegrationTests { + + @Autowired + private Cache peerCache; + + @Autowired + @Qualifier("testPeerCacheConfigurerOne") + private TestPeerCacheConfigurer configurerOne; + + @Autowired + @Qualifier("testPeerCacheConfigurerTwo") + private TestPeerCacheConfigurer configurerTwo; + + @Before + public void setup() { + assertThat(this.peerCache).isNotNull(); + } + + private void assertTestPeerCacheConfigurerCalledSuccessully(TestPeerCacheConfigurer peerCacheConfigurer, + String... beanNames) { + + assertThat(peerCacheConfigurer).isNotNull(); + assertThat(peerCacheConfigurer).hasSize(beanNames.length); + assertThat(peerCacheConfigurer).contains(beanNames); + } + + @Test + public void peerCacheConfigurerOneCalledSuccessfully() { + assertTestPeerCacheConfigurerCalledSuccessully(this.configurerOne, "gemfireCache"); + } + + @Test + public void peerCacheConfigurerTwoCalledSuccessfully() { + assertTestPeerCacheConfigurerCalledSuccessully(this.configurerTwo, "gemfireCache"); + } + + @PeerCacheApplication + static class TestConfiguration { + + @Bean + GemfireTestBeanPostProcessor testBeanPostProcessor() { + return new GemfireTestBeanPostProcessor(); + } + + @Bean + TestPeerCacheConfigurer testPeerCacheConfigurerOne() { + return new TestPeerCacheConfigurer(); + } + + @Bean + TestPeerCacheConfigurer testPeerCacheConfigurerTwo() { + return new TestPeerCacheConfigurer(); + } + + @Bean + String nonRelevantBean() { + return "test"; + } + } + + static class TestPeerCacheConfigurer implements Iterable, PeerCacheConfigurer { + + private Set beanNames = new HashSet<>(); + + @Override + public void configure(String beanName, CacheFactoryBean bean) { + this.beanNames.add(beanName); + } + + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.beanNames).iterator(); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/PoolConfigurerIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/PoolConfigurerIntegrationTests.java new file mode 100644 index 00000000..56a1c121 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/PoolConfigurerIntegrationTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2017 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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.gemfire.client.PoolFactoryBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests for {@link PoolConfigurer}. + * + * @author John Blum + * @see org.junit.Test + * @see org.apache.geode.cache.client.Pool + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.data.gemfire.client.PoolFactoryBean + * @see org.springframework.data.gemfire.config.annotation.AddPoolConfiguration + * @see org.springframework.data.gemfire.config.annotation.AddPoolsConfiguration + * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer + * @see org.springframework.data.gemfire.config.annotation.EnablePool + * @see org.springframework.data.gemfire.config.annotation.EnablePools + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @since 1.1.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@SuppressWarnings("unused") +public class PoolConfigurerIntegrationTests { + + @Autowired + @Qualifier("configurerOne") + private TestPoolConfigurer configurerOne; + + @Autowired + @Qualifier("configurerTwo") + private TestPoolConfigurer configurerTwo; + + protected void assertPoolConfigurerCalled(TestPoolConfigurer configurer, String... beanNames) { + assertThat(configurer).isNotNull(); + assertThat(configurer).hasSize(beanNames.length); + assertThat(configurer).contains(beanNames); + } + + @Test + public void poolConfigurerOneCalledSuccessfully() { + assertPoolConfigurerCalled(this.configurerOne, "poolOne", "poolTwo", "poolThree"); + } + + @Test + public void poolConfigurerTwoCalledSuccessfully() { + assertPoolConfigurerCalled(this.configurerTwo, "poolOne", "poolTwo", "poolThree"); + } + + @Configuration + @EnablePools(pools = { + @EnablePool(name = "poolOne"), + @EnablePool(name = "poolTwo"), + @EnablePool(name = "poolThree"), + }) + static class TestConfiguration { + + @Bean + TestPoolConfigurer configurerOne() { + return new TestPoolConfigurer(); + } + + @Bean + TestPoolConfigurer configurerTwo() { + return new TestPoolConfigurer(); + } + + @Bean + Object nonRelevantBean() { + return "test"; + } + } + + static class TestPoolConfigurer implements Iterable, PoolConfigurer { + + private final Set beanNames = new HashSet<>(); + + @Override + public void configure(String beanName, PoolFactoryBean bean) { + this.beanNames.add(beanName); + } + + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.beanNames).iterator(); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/annotation/RegionConfigurerIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/config/annotation/RegionConfigurerIntegrationTests.java new file mode 100644 index 00000000..b5001c5b --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/config/annotation/RegionConfigurerIntegrationTests.java @@ -0,0 +1,204 @@ +/* + * Copyright 2017 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.config.annotation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Optional; +import java.util.Set; + +import org.apache.geode.cache.GemFireCache; +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.data.gemfire.PartitionedRegionFactoryBean; +import org.springframework.data.gemfire.RegionFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.config.annotation.test.entities.CollocatedPartitionRegionEntity; +import org.springframework.data.gemfire.config.annotation.test.entities.NonEntity; +import org.springframework.data.gemfire.mapping.annotation.ClientRegion; +import org.springframework.data.gemfire.mapping.annotation.LocalRegion; +import org.springframework.data.gemfire.mapping.annotation.PartitionRegion; +import org.springframework.data.gemfire.mapping.annotation.ReplicateRegion; +import org.springframework.data.gemfire.test.GemfireTestBeanPostProcessor; + +/** + * Integration tests for {@link RegionConfigurer}. + * + * @author John Blum + * @see org.junit.Test + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @see org.springframework.context.ConfigurableApplicationContext + * @see org.springframework.context.annotation.AnnotationConfigApplicationContext + * @see org.springframework.data.gemfire.RegionFactoryBean + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean + * @see org.springframework.data.gemfire.config.annotation.RegionConfigurer + * @see org.springframework.data.gemfire.test.GemfireTestBeanPostProcessor + * @since 1.1.0 + */ +public class RegionConfigurerIntegrationTests { + + private ConfigurableApplicationContext applicationContext; + + @After + public void tearDown() { + Optional.ofNullable(this.applicationContext).ifPresent(ConfigurableApplicationContext::close); + } + + /* (non-Javadoc) */ + private void assertRegionConfigurerInvocations(TestRegionConfigurer regionConfigurer, String... regionBeanNames) { + assertThat(regionConfigurer).isNotNull(); + assertThat(regionConfigurer).contains(regionBeanNames); + assertThat(regionConfigurer).hasSize(regionBeanNames.length); + } + + /* (non-Javadoc) */ + private ConfigurableApplicationContext newApplicationContext(Class... annotatedClasses) { + ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(annotatedClasses); + applicationContext.registerShutdownHook(); + return applicationContext; + } + + @Test + public void clientRegionConfigurersCalledSuccessfully() { + + this.applicationContext = newApplicationContext(ClientTestConfiguration.class); + + assertThat(this.applicationContext).isNotNull(); + assertThat(this.applicationContext.containsBean("Sessions")).isTrue(); + assertThat(this.applicationContext.containsBean("GenericRegionEntity")).isTrue(); + assertThat(this.applicationContext.containsBean("Test")).isTrue(); + assertThat(this.applicationContext.containsBean("testRegionConfigurerOne")).isTrue(); + assertThat(this.applicationContext.containsBean("testRegionConfigurerTwo")).isTrue(); + + assertRegionConfigurerInvocations( + this.applicationContext.getBean("testRegionConfigurerOne", TestRegionConfigurer.class), + "GenericRegionEntity", "Sessions"); + + assertRegionConfigurerInvocations( + this.applicationContext.getBean("testRegionConfigurerTwo", TestRegionConfigurer.class), + "GenericRegionEntity", "Sessions"); + } + + @Test + public void peerRegionConfigurersCalledSuccessfully() { + + this.applicationContext = newApplicationContext(PeerTestConfiguration.class); + + assertThat(this.applicationContext).isNotNull(); + assertThat(this.applicationContext.containsBean("Customers")).isTrue(); + assertThat(this.applicationContext.containsBean("GenericRegionEntity")).isTrue(); + assertThat(this.applicationContext.containsBean("Test")).isTrue(); + assertThat(this.applicationContext.containsBean("testRegionConfigurerOne")).isTrue(); + assertThat(this.applicationContext.containsBean("testRegionConfigurerTwo")).isTrue(); + + assertRegionConfigurerInvocations( + this.applicationContext.getBean("testRegionConfigurerOne", TestRegionConfigurer.class), + "Customers", "GenericRegionEntity"); + + assertRegionConfigurerInvocations( + this.applicationContext.getBean("testRegionConfigurerTwo", TestRegionConfigurer.class), + "Customers", "GenericRegionEntity"); + } + + @SuppressWarnings("unused") + static class AbstractTestConfiguration { + + @Bean + GemfireTestBeanPostProcessor testBeanPostProcessor() { + return new GemfireTestBeanPostProcessor(); + } + + @Bean + TestRegionConfigurer testRegionConfigurerOne() { + return new TestRegionConfigurer(); + } + + @Bean + TestRegionConfigurer testRegionConfigurerTwo() { + return new TestRegionConfigurer(); + } + + @Bean + String nonRelevantBean() { + return "test"; + } + } + + @ClientCacheApplication + @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class, + excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, + classes = { LocalRegion.class, PartitionRegion.class, ReplicateRegion.class })) + @SuppressWarnings("unused") + static class ClientTestConfiguration extends AbstractTestConfiguration { + + @Bean(name = "Test") + ClientRegionFactoryBean testRegion(GemFireCache gemfireCache) { + ClientRegionFactoryBean testRegionFactory = new ClientRegionFactoryBean<>(); + testRegionFactory.setCache(gemfireCache); + return testRegionFactory; + } + } + + @PeerCacheApplication + @EnableEntityDefinedRegions(basePackageClasses = NonEntity.class, + excludeFilters = { + @ComponentScan.Filter(type = FilterType.ANNOTATION, + classes = { ClientRegion.class, LocalRegion.class, ReplicateRegion.class }), + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, + classes = CollocatedPartitionRegionEntity.class) + } + ) + @SuppressWarnings("unused") + static class PeerTestConfiguration extends AbstractTestConfiguration { + + @Bean(name = "Test") + PartitionedRegionFactoryBean testRegion(GemFireCache gemfireCache) { + PartitionedRegionFactoryBean testRegionFactory = new PartitionedRegionFactoryBean<>(); + testRegionFactory.setCache(gemfireCache); + return testRegionFactory; + } + } + + private static class TestRegionConfigurer implements Iterable, RegionConfigurer { + + private final Set beanNames = new HashSet<>(); + + @Override + public void configure(String beanName, RegionFactoryBean bean) { + this.beanNames.add(beanName); + } + + @Override + public void configure(String beanName, ClientRegionFactoryBean bean) { + this.beanNames.add(beanName); + } + + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(this.beanNames).iterator(); + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/config/xml/ClientRegionNamespaceTest.java b/src/test/java/org/springframework/data/gemfire/config/xml/ClientRegionNamespaceTest.java index 5e242159..5727fb89 100644 --- a/src/test/java/org/springframework/data/gemfire/config/xml/ClientRegionNamespaceTest.java +++ b/src/test/java/org/springframework/data/gemfire/config/xml/ClientRegionNamespaceTest.java @@ -16,21 +16,23 @@ package org.springframework.data.gemfire.config.xml; +import static java.util.Arrays.stream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; import java.io.File; -import java.io.FilenameFilter; import java.util.concurrent.atomic.AtomicReference; import org.apache.geode.cache.CacheListener; @@ -46,6 +48,7 @@ import org.apache.geode.cache.LoaderHelper; import org.apache.geode.cache.Region; import org.apache.geode.cache.RegionAttributes; import org.apache.geode.cache.client.ClientCache; +import org.apache.geode.cache.client.ClientRegionFactory; import org.apache.geode.cache.client.ClientRegionShortcut; import org.apache.geode.cache.util.CacheWriterAdapter; import org.apache.geode.compression.Compressor; @@ -66,12 +69,13 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.ObjectUtils; /** - * The ClientRegionNamespaceTest class is a test suite of test cases testing the contract and functionality - * of GemFire Client Region namespace support in SDG. + * Unit tests for Spring Data GemFire's XML namespace support for client {@link Region Regions}. * * @author Costin Leau * @author David Turanski * @author John Blum + * @see org.apache.geode.cache.Region + * @see org.apache.geode.cache.client.ClientCache * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean * @see org.springframework.data.gemfire.config.xml.ClientRegionParser */ @@ -81,35 +85,29 @@ import org.springframework.util.ObjectUtils; public class ClientRegionNamespaceTest { @Autowired - private ApplicationContext context; + private ApplicationContext applicationContext; @AfterClass public static void tearDown() { - for (String name : new File(".").list(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.startsWith("BACKUP"); - } - })) { - new File(name).delete(); - } + stream(nullSafeArray(new File(".").list((dir, name) -> name.startsWith("BACKUP")), String.class)) + .forEach(fileName -> new File(fileName).delete()); } @Test public void testBeanNames() throws Exception { - assertTrue(context.containsBean("SimpleRegion")); - assertTrue(context.containsBean("Publisher")); - assertTrue(context.containsBean("ComplexRegion")); - assertTrue(context.containsBean("PersistentRegion")); - assertTrue(context.containsBean("OverflowRegion")); - assertTrue(context.containsBean("Compressed")); + assertTrue(applicationContext.containsBean("SimpleRegion")); + assertTrue(applicationContext.containsBean("Publisher")); + assertTrue(applicationContext.containsBean("ComplexRegion")); + assertTrue(applicationContext.containsBean("PersistentRegion")); + assertTrue(applicationContext.containsBean("OverflowRegion")); + assertTrue(applicationContext.containsBean("Compressed")); } @Test public void testSimpleClientRegion() throws Exception { - assertTrue(context.containsBean("simple")); + assertTrue(applicationContext.containsBean("simple")); - Region simple = context.getBean("simple", Region.class); + Region simple = applicationContext.getBean("simple", Region.class); assertNotNull("The 'SimpleRegion' Client Region was not properly configured and initialized!", simple); assertEquals("SimpleRegion", simple.getName()); @@ -121,9 +119,10 @@ public class ClientRegionNamespaceTest { @Test @SuppressWarnings("rawtypes") public void testPublishingClientRegion() throws Exception { - assertTrue(context.containsBean("empty")); + assertTrue(applicationContext.containsBean("empty")); - ClientRegionFactoryBean emptyClientRegionFactoryBean = context.getBean("&empty", ClientRegionFactoryBean.class); + ClientRegionFactoryBean emptyClientRegionFactoryBean = applicationContext + .getBean("&empty", ClientRegionFactoryBean.class); assertNotNull(emptyClientRegionFactoryBean); assertEquals(DataPolicy.EMPTY, TestUtils.readField("dataPolicy", emptyClientRegionFactoryBean)); @@ -135,9 +134,10 @@ public class ClientRegionNamespaceTest { @Test @SuppressWarnings("rawtypes") public void testComplexClientRegion() throws Exception { - assertTrue(context.containsBean("complex")); + assertTrue(applicationContext.containsBean("complex")); - ClientRegionFactoryBean complexClientRegionFactoryBean = context.getBean("&complex", ClientRegionFactoryBean.class); + ClientRegionFactoryBean complexClientRegionFactoryBean = applicationContext + .getBean("&complex", ClientRegionFactoryBean.class); assertNotNull(complexClientRegionFactoryBean); @@ -145,7 +145,7 @@ public class ClientRegionNamespaceTest { assertFalse(ObjectUtils.isEmpty(cacheListeners)); assertEquals(2, cacheListeners.length); - assertSame(cacheListeners[0], context.getBean("c-listener")); + assertSame(cacheListeners[0], applicationContext.getBean("c-listener")); assertTrue(cacheListeners[1] instanceof SimpleCacheListener); assertNotSame(cacheListeners[0], cacheListeners[1]); @@ -161,9 +161,9 @@ public class ClientRegionNamespaceTest { @Test @SuppressWarnings({ "deprecation", "rawtypes" }) public void testPersistentClientRegion() throws Exception { - assertTrue(context.containsBean("persistent")); + assertTrue(applicationContext.containsBean("persistent")); - Region persistent = context.getBean("persistent", Region.class); + Region persistent = applicationContext.getBean("persistent", Region.class); assertNotNull("The 'PersistentRegion' Region was not properly configured and initialized!", persistent); assertEquals("PersistentRegion", persistent.getName()); @@ -179,9 +179,10 @@ public class ClientRegionNamespaceTest { @Test @SuppressWarnings("rawtypes") public void testOverflowClientRegion() throws Exception { - assertTrue(context.containsBean("overflow")); + assertTrue(applicationContext.containsBean("overflow")); - ClientRegionFactoryBean overflowClientRegionFactoryBean = context.getBean("&overflow", ClientRegionFactoryBean.class); + ClientRegionFactoryBean overflowClientRegionFactoryBean = applicationContext + .getBean("&overflow", ClientRegionFactoryBean.class); assertNotNull(overflowClientRegionFactoryBean); assertEquals("diskStore", TestUtils.readField("diskStoreName", overflowClientRegionFactoryBean)); @@ -203,9 +204,9 @@ public class ClientRegionNamespaceTest { @Test public void testClientRegionWithCacheLoaderAndCacheWriter() throws Exception { - assertTrue(context.containsBean("loadWithWrite")); + assertTrue(applicationContext.containsBean("loadWithWrite")); - ClientRegionFactoryBean factory = context.getBean("&loadWithWrite", ClientRegionFactoryBean.class); + ClientRegionFactoryBean factory = applicationContext.getBean("&loadWithWrite", ClientRegionFactoryBean.class); assertNotNull(factory); assertEquals("LoadedFullOfWrites", TestUtils.readField("name", factory)); @@ -216,9 +217,9 @@ public class ClientRegionNamespaceTest { @Test public void testCompressedReplicateRegion() { - assertTrue(context.containsBean("Compressed")); + assertTrue(applicationContext.containsBean("Compressed")); - Region compressed = context.getBean("Compressed", Region.class); + Region compressed = applicationContext.getBean("Compressed", Region.class); assertNotNull("The 'Compressed' Client Region was not properly configured and initialized!", compressed); assertEquals("Compressed", compressed.getName()); @@ -235,9 +236,9 @@ public class ClientRegionNamespaceTest { @Test @SuppressWarnings("unchecked") public void testClientRegionWithAttributes() { - assertTrue(context.containsBean("client-with-attributes")); + assertTrue(applicationContext.containsBean("client-with-attributes")); - Region clientRegion = context.getBean("client-with-attributes", Region.class); + Region clientRegion = applicationContext.getBean("client-with-attributes", Region.class); assertNotNull("The 'client-with-attributes' Client Region was not properly configured and initialized!", clientRegion); assertEquals("client-with-attributes", clientRegion.getName()); @@ -258,9 +259,11 @@ public class ClientRegionNamespaceTest { @Test @SuppressWarnings("unchecked") public void testClientRegionWithRegisteredInterests() throws Exception { - assertTrue(context.containsBean("client-with-interests")); - ClientRegionFactoryBean factoryBean = context.getBean("&client-with-interests", ClientRegionFactoryBean.class); + assertTrue(applicationContext.containsBean("client-with-interests")); + + ClientRegionFactoryBean factoryBean = + applicationContext.getBean("&client-with-interests", ClientRegionFactoryBean.class); assertNotNull(factoryBean); @@ -276,10 +279,11 @@ public class ClientRegionNamespaceTest { assertNotNull(mockClientRegion); - verify(mockClientRegion, times(1)).registerInterest(eq(".*"), eq(InterestResultPolicy.KEYS), - eq(true), eq(false)); - verify(mockClientRegion, times(1)).registerInterestRegex(eq("keyPrefix.*"), eq(InterestResultPolicy.KEYS_VALUES), - eq(true), eq(false)); + verify(mockClientRegion, times(1)).registerInterest(eq(".*"), + eq(InterestResultPolicy.KEYS), eq(true), eq(false)); + + verify(mockClientRegion, times(1)).registerInterestRegex(eq("keyPrefix.*"), + eq(InterestResultPolicy.KEYS_VALUES), eq(true), eq(false)); } protected void assertInterest(final boolean expectedDurable, final boolean expectedReceiveValues, @@ -300,38 +304,41 @@ public class ClientRegionNamespaceTest { return null; } - public static final class MockCacheFactoryBean implements FactoryBean, InitializingBean { + static final class MockCacheFactoryBean implements FactoryBean, InitializingBean { - protected static final AtomicReference MOCK_REGION_REF = new AtomicReference(null); + static final AtomicReference MOCK_REGION_REF = new AtomicReference(null); private ClientCache mockClientCache; @Override @SuppressWarnings("unchecked") public void afterPropertiesSet() throws Exception { - mockClientCache = mock(ClientCache.class, ClientRegionNamespaceTest.class.getSimpleName() - .concat(".MockClientCache")); + this.mockClientCache = mock(ClientCache.class, + ClientRegionNamespaceTest.class.getSimpleName().concat(".MockClientCache")); - MOCK_REGION_REF.compareAndSet(null, mock(Region.class, ClientRegionNamespaceTest.class.getSimpleName() - .concat(".MockClientRegion"))); + ClientRegionFactory mockClientRegionFactory = mock(ClientRegionFactory.class, + ClientRegionNamespaceTest.class.getSimpleName().concat("MockClientRegionFactory")); - when(mockClientCache.getRegion(anyString())).thenReturn(MOCK_REGION_REF.get()); + when(this.mockClientCache.createClientRegionFactory(any(ClientRegionShortcut.class))) + .thenReturn(mockClientRegionFactory); + + Region mockRegion = mock(Region.class, + ClientRegionNamespaceTest.class.getSimpleName().concat(".MockClientRegion")); + + when(mockClientRegionFactory.create(anyString())).thenReturn(mockRegion); + + MOCK_REGION_REF.compareAndSet(null, mockRegion); } @Override public ClientCache getObject() throws Exception { - return mockClientCache; + return this.mockClientCache; } @Override public Class getObjectType() { return ClientCache.class; } - - @Override - public boolean isSingleton() { - return true; - } } public static final class TestCacheLoader implements CacheLoader { @@ -371,6 +378,5 @@ public class ClientRegionNamespaceTest { public String toString() { return this.name; } - } } diff --git a/src/test/java/org/springframework/data/gemfire/config/xml/InvalidRegionDefinitionUsingBeansNamespaceTest.java b/src/test/java/org/springframework/data/gemfire/config/xml/InvalidRegionDefinitionUsingBeansNamespaceTest.java index b9e6b767..7c4e3455 100644 --- a/src/test/java/org/springframework/data/gemfire/config/xml/InvalidRegionDefinitionUsingBeansNamespaceTest.java +++ b/src/test/java/org/springframework/data/gemfire/config/xml/InvalidRegionDefinitionUsingBeansNamespaceTest.java @@ -37,15 +37,18 @@ import org.springframework.data.gemfire.RegionFactoryBean; */ public class InvalidRegionDefinitionUsingBeansNamespaceTest { + private static final String CONFIG_LOCATION = + "org/springframework/data/gemfire/config/xml/InvalidDataPolicyPersistentAttributeSettingsBeansNamespaceTest.xml"; + @Test(expected = IllegalArgumentException.class) public void testInvalidDataPolicyPersistentAttributeSettings() { try { - new ClassPathXmlApplicationContext( - "org/springframework/data/gemfire/config/xml/InvalidDataPolicyPersistentAttributeSettingsBeansNamespaceTest.xml"); + new ClassPathXmlApplicationContext(CONFIG_LOCATION); } catch (BeanCreationException expected) { assertTrue(expected.getCause() instanceof IllegalArgumentException); - assertEquals("Data Policy 'REPLICATE' is invalid when persistent is true.", expected.getCause().getMessage()); + assertEquals("Data Policy [REPLICATE] is invalid when persistent is true.", expected.getCause().getMessage()); + throw (IllegalArgumentException) expected.getCause(); } } @@ -53,5 +56,4 @@ public class InvalidRegionDefinitionUsingBeansNamespaceTest { @SuppressWarnings("unused") public static final class TestRegionFactoryBean extends RegionFactoryBean { } - } diff --git a/src/test/java/org/springframework/data/gemfire/config/xml/SubRegionWithInvalidDataPolicyTest.java b/src/test/java/org/springframework/data/gemfire/config/xml/SubRegionWithInvalidDataPolicyTest.java index 13d30f8d..f6925655 100644 --- a/src/test/java/org/springframework/data/gemfire/config/xml/SubRegionWithInvalidDataPolicyTest.java +++ b/src/test/java/org/springframework/data/gemfire/config/xml/SubRegionWithInvalidDataPolicyTest.java @@ -44,9 +44,9 @@ public class SubRegionWithInvalidDataPolicyTest { "/org/springframework/data/gemfire/config/xml/subregion-with-invalid-datapolicy.xml"); } catch (XmlBeanDefinitionStoreException expected) { - //expected.printStackTrace(System.err); assertTrue(expected.getCause() instanceof SAXParseException); assertTrue(expected.getCause().getMessage().contains("PERSISTENT_PARTITION")); + throw expected; } } @@ -58,13 +58,12 @@ public class SubRegionWithInvalidDataPolicyTest { "/org/springframework/data/gemfire/config/xml/subregion-with-inconsistent-datapolicy-persistent-settings.xml"); } catch (BeanCreationException expected) { - //expected.printStackTrace(System.err); assertTrue(expected.getMessage().contains("Error creating bean with name '/Parent/Child'")); assertTrue(expected.getCause() instanceof IllegalArgumentException); - assertEquals("Data Policy 'REPLICATE' is invalid when persistent is true.", + assertEquals("Data Policy [REPLICATE] is invalid when persistent is true.", expected.getCause().getMessage()); + throw expected; } } - } diff --git a/src/test/java/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest.java index b11ece71..ebdbfc94 100644 --- a/src/test/java/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest.java +++ b/src/test/java/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest.java @@ -84,6 +84,7 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe @BeforeClass public static void startGemFireServer() throws Exception { + int availablePort = findAvailablePort(); gemfireServer = run(ServerProcess.class, @@ -101,8 +102,10 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe stop(gemfireServer); } - protected PdxInstance toPdxInstance(final Map pdxData) { - PdxInstanceFactory pdxInstanceFactory = gemfireClientCache.createPdxInstanceFactory(pdxData.get("@type").toString()); + private PdxInstance toPdxInstance(Map pdxData) { + + PdxInstanceFactory pdxInstanceFactory = + gemfireClientCache.createPdxInstanceFactory(pdxData.get("@type").toString()); for (Map.Entry entry : pdxData.entrySet()) { pdxInstanceFactory.writeObject(entry.getKey(), entry.getValue()); @@ -112,9 +115,10 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe } @Test - public void testConvertedFunctionArgumentTypes() { - Class[] argumentTypes = functionExecutions.captureConvertedArgumentTypes("test", 1, Boolean.TRUE, - new Person("Jon", "Doe"), Gender.MALE); + public void convertedFunctionArgumentTypes() { + + Class[] argumentTypes = functionExecutions.captureConvertedArgumentTypes("test", 1, + Boolean.TRUE, new Person("Jon", "Doe"), Gender.MALE); assertNotNull(argumentTypes); assertEquals(5, argumentTypes.length); @@ -126,9 +130,10 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe } @Test - public void testUnconvertedFunctionArgumentTypes() { - Class[] argumentTypes = functionExecutions.captureUnconvertedArgumentTypes("test", 2, Boolean.FALSE, - new Person("Jane", "Doe"), Gender.FEMALE); + public void unconvertedFunctionArgumentTypes() { + + Class[] argumentTypes = functionExecutions.captureUnconvertedArgumentTypes("test", 2, + Boolean.FALSE, new Person("Jane", "Doe"), Gender.FEMALE); assertNotNull(argumentTypes); assertEquals(5, argumentTypes.length); @@ -140,13 +145,14 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe } @Test - public void testGetAddressFieldValue() { + public void getAddressFieldValue() { assertEquals("Portland", functionExecutions.getAddressField(new Address( "100 Main St.", "Portland", "OR", "97205"), "city")); } @Test - public void testPdxDataFieldValue() { + public void pdxDataFieldValue() { + Map pdxData = new HashMap(3); pdxData.put("@type", "x.y.z.domain.MyApplicationDomainType"); @@ -162,6 +168,7 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe public static class ApplicationDomainFunctions { private Class[] getArgumentTypes(final Object... arguments) { + Class[] argumentTypes = new Class[arguments.length]; int index = 0; @@ -174,25 +181,27 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe } @GemfireFunction - public Class[] captureConvertedArgumentTypes(final String stringValue, final Integer integerValue, - final Boolean booleanValue, final Person person, final Gender gender) { + public Class[] captureConvertedArgumentTypes(String stringValue, Integer integerValue, Boolean booleanValue, + Person person, Gender gender) { + return getArgumentTypes(stringValue, integerValue, booleanValue, person, gender); } @GemfireFunction - public Class[] captureUnconvertedArgumentTypes(final String stringValue, final Integer integerValue, - final Boolean booleanValue, final Object domainObject, final Object enumValue) { + public Class[] captureUnconvertedArgumentTypes(String stringValue, Integer integerValue, Boolean booleanValue, + Object domainObject, Object enumValue) { + return getArgumentTypes(stringValue, integerValue, booleanValue, domainObject, enumValue); } @GemfireFunction - public String getAddressField(final PdxInstance address, final String fieldName) { - Assert.isTrue(Address.class.getName().equals(address.getClassName())); + public String getAddressField(PdxInstance address, String fieldName) { + Assert.isTrue(Address.class.getName().equals(address.getClassName()), "Address is not the correct type"); return String.valueOf(address.getField(fieldName)); } @GemfireFunction - public Object getDataField(final PdxInstance data, final String fieldName) { + public Object getDataField(PdxInstance data, String fieldName) { return data.getField(fieldName); } } @@ -204,11 +213,12 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe private final String state; // refactor; use Enum! private final String zipCode; - public Address(final String street, final String city, final String state, final String zipCode) { + public Address(String street, String city, String state, String zipCode) { Assert.hasText("The Address 'street' must be specified", street); Assert.hasText("The Address 'city' must be specified", city); Assert.hasText("The Address 'state' must be specified", state); Assert.hasText("The Address 'zipCode' must be specified", zipCode); + this.street = street; this.city = city; this.state = state; @@ -233,6 +243,7 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe @Override public boolean equals(final Object obj) { + if (obj == this) { return true; } @@ -251,11 +262,14 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe @Override public int hashCode() { + int hashValue = 17; + hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getStreet()); hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getCity()); hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getState()); hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getZipCode()); + return hashValue; } @@ -275,9 +289,10 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe private final String firstName; private final String lastName; - public Person(final String firstName, final String lastName) { + public Person(String firstName, String lastName) { Assert.hasText(firstName, "The person's first name must be specified!"); Assert.hasText(lastName, "The person's last name must be specified!"); + this.firstName = firstName; this.lastName = lastName; } @@ -292,6 +307,7 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe @Override public boolean equals(final Object obj) { + if (obj == this) { return true; } @@ -308,9 +324,12 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe @Override public int hashCode() { + int hashValue = 17; + hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getFirstName()); hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getLastName()); + return hashValue; } @@ -324,18 +343,18 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe private final PdxSerializer[] pdxSerializers; - private ComposablePdxSerializer(final PdxSerializer[] pdxSerializers) { + private ComposablePdxSerializer(PdxSerializer[] pdxSerializers) { this.pdxSerializers = pdxSerializers; } - public static PdxSerializer compose(final PdxSerializer... pdxSerializers) { + public static PdxSerializer compose(PdxSerializer... pdxSerializers) { return (pdxSerializers == null ? null : (pdxSerializers.length == 1 ? pdxSerializers[0] : new ComposablePdxSerializer(pdxSerializers))); } @Override - public boolean toData(final Object obj, final PdxWriter out) { - for (PdxSerializer pdxSerializer : pdxSerializers) { + public boolean toData(Object obj, PdxWriter out) { + for (PdxSerializer pdxSerializer : this.pdxSerializers) { if (pdxSerializer.toData(obj, out)) { return true; } @@ -345,8 +364,8 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe } @Override - public Object fromData(final Class type, final PdxReader in) { - for (PdxSerializer pdxSerializer : pdxSerializers) { + public Object fromData(Class type, final PdxReader in) { + for (PdxSerializer pdxSerializer : this.pdxSerializers) { Object obj = pdxSerializer.fromData(type, in); if (obj != null) { @@ -393,7 +412,8 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe public static class AddressPdxSerializer implements PdxSerializer { @Override - public boolean toData(final Object obj, final PdxWriter out) { + public boolean toData(Object obj, PdxWriter out) { + if (obj instanceof Address) { Address address = (Address) obj; out.writeString("street", address.getStreet()); @@ -407,7 +427,8 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe } @Override - public Object fromData(final Class type, final PdxReader in) { + public Object fromData(Class type, PdxReader in) { + if (Address.class.isAssignableFrom(type)) { return new Address(in.readString("street"), in.readString("city"), in.readString("state"), in.readString("zipCode")); @@ -419,7 +440,8 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe public static class PersonPdxSerializer implements PdxSerializer { @Override - public boolean toData(final Object obj, final PdxWriter out) { + public boolean toData(Object obj, PdxWriter out) { + if (obj instanceof Person) { Person person = (Person) obj; out.writeString("firstName", person.getFirstName()); @@ -431,7 +453,7 @@ public class ClientCacheFunctionExecutionWithPdxIntegrationTest extends ClientSe } @Override - public Object fromData(final Class type, final PdxReader in) { + public Object fromData(Class type, PdxReader in) { if (Person.class.isAssignableFrom(type)) { return new Person(in.readString("firstName"), in.readString("lastName")); } diff --git a/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionClientCacheTests.java b/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionClientCacheTests.java index 38bdbdf1..c1c7ca40 100644 --- a/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionClientCacheTests.java +++ b/src/test/java/org/springframework/data/gemfire/function/config/FunctionExecutionClientCacheTests.java @@ -32,44 +32,45 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import org.springframework.data.gemfire.function.execution.GemfireOnServerFunctionTemplate; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; /** * @author David Turanski * */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(classes = { TestClientCacheConfig.class }) public class FunctionExecutionClientCacheTests { @Autowired - ApplicationContext context; + ApplicationContext applicationContext; @Test public void contextCreated() throws Exception { - ClientCache cache = context.getBean("gemfireCache", ClientCache.class); - Pool pool = context.getBean("gemfirePool", Pool.class); + + ClientCache cache = applicationContext.getBean("gemfireCache", ClientCache.class); + Pool pool = applicationContext.getBean("gemfirePool", Pool.class); assertEquals("gemfirePool", pool.getName()); assertTrue(cache.getDefaultPool().getLocators().isEmpty()); assertEquals(1, cache.getDefaultPool().getServers().size()); assertEquals(pool.getServers().get(0), cache.getDefaultPool().getServers().get(0)); - Region region = context.getBean("r1", Region.class); + Region region = applicationContext.getBean("r1", Region.class); assertEquals("gemfirePool", region.getAttributes().getPoolName()); - GemfireOnServerFunctionTemplate template = context.getBean(GemfireOnServerFunctionTemplate.class); + GemfireOnServerFunctionTemplate template = applicationContext.getBean(GemfireOnServerFunctionTemplate.class); assertTrue(template.getResultCollector() instanceof MyResultCollector); } - } @Configuration @ImportResource("/org/springframework/data/gemfire/function/config/FunctionExecutionCacheClientTests-context.xml") @EnableGemfireFunctionExecutions(basePackages = "org.springframework.data.gemfire.function.config.three") class TestClientCacheConfig { + @Bean MyResultCollector myResultCollector() { return new MyResultCollector(); @@ -115,5 +116,4 @@ class MyResultCollector implements ResultCollector { public Object getResult(long arg0, TimeUnit arg1) throws FunctionException, InterruptedException { return null; } - } diff --git a/src/test/java/org/springframework/data/gemfire/repository/config/GemfireRepositoriesRegistrarIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/repository/config/GemfireRepositoriesRegistrarIntegrationTest.java index f4fdf4c4..778ef52a 100644 --- a/src/test/java/org/springframework/data/gemfire/repository/config/GemfireRepositoriesRegistrarIntegrationTest.java +++ b/src/test/java/org/springframework/data/gemfire/repository/config/GemfireRepositoriesRegistrarIntegrationTest.java @@ -23,14 +23,12 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; -import org.springframework.data.gemfire.CacheFactoryBean; import org.springframework.data.gemfire.LocalRegionFactoryBean; +import org.springframework.data.gemfire.config.annotation.PeerCacheApplication; import org.springframework.data.gemfire.repository.sample.Person; import org.springframework.data.gemfire.repository.sample.PersonRepository; import org.springframework.data.gemfire.test.GemfireTestApplicationContextInitializer; -import org.springframework.data.gemfire.test.MockCacheFactoryBean; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -39,29 +37,24 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * repository configuration). * * @author Oliver Gierke + * @author John Blum */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(initializers=GemfireTestApplicationContextInitializer.class) public class GemfireRepositoriesRegistrarIntegrationTest { - @Configuration + @PeerCacheApplication @EnableGemfireRepositories(value = "org.springframework.data.gemfire.repository.sample", includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = org.springframework.data.gemfire.repository.sample.PersonRepository.class)) static class Config { @Bean - public GemFireCache cache() throws Exception { + public Region simple(GemFireCache gemfireCache) throws Exception { - CacheFactoryBean factory = new MockCacheFactoryBean(); - return factory.getObject(); - } + LocalRegionFactoryBean factory = new LocalRegionFactoryBean<>(); - @Bean - public Region simple() throws Exception { - - LocalRegionFactoryBean factory = new LocalRegionFactoryBean(); - factory.setCache(cache()); + factory.setCache(gemfireCache); factory.setName("simple"); factory.afterPropertiesSet(); @@ -75,5 +68,4 @@ public class GemfireRepositoriesRegistrarIntegrationTest { @Test public void bootstrapsRepositoriesCorrectly() { } - } diff --git a/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBeanUnitTests.java b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBeanUnitTests.java index 2f872bc5..a1b1cb1e 100644 --- a/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBeanUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/search/lucene/LuceneIndexFactoryBeanUnitTests.java @@ -111,7 +111,7 @@ public class LuceneIndexFactoryBeanUnitTests { doReturn(mockCache).when(factoryBean).resolveCache(); doReturn(mockLuceneService).when(factoryBean).resolveLuceneService(); doReturn("/Example").when(factoryBean).resolveRegionPath(); - doReturn(mockLuceneIndex).when(factoryBean).createIndex(eq("ExampleIndex"), eq("/Example")); + doReturn(mockLuceneIndex).when(factoryBean).createLuceneIndex(eq("ExampleIndex"), eq("/Example")); factoryBean.setIndexName("ExampleIndex"); @@ -122,10 +122,10 @@ public class LuceneIndexFactoryBeanUnitTests { assertThat(factoryBean.getObject()).isEqualTo(mockLuceneIndex); verify(factoryBean, times(1)).resolveCache(); - verify(factoryBean, times(1)).resolveLuceneService(); + verify(factoryBean, times(2)).resolveLuceneService(); verify(factoryBean, times(1)).resolveRegionPath(); verify(factoryBean, times(1)) - .createIndex(eq("ExampleIndex"), eq("/Example")); + .createLuceneIndex(eq("ExampleIndex"), eq("/Example")); } @Test @@ -138,7 +138,7 @@ public class LuceneIndexFactoryBeanUnitTests { } @Test - public void createIndexWithAllFields() { + public void createLuceneIndexWithAllFields() { factoryBean.setLuceneService(mockLuceneService); when(mockLuceneService.getIndex(eq("ExampleIndex"), eq("/Example"))).thenReturn(mockLuceneIndex); @@ -146,7 +146,7 @@ public class LuceneIndexFactoryBeanUnitTests { assertThat(factoryBean.getFieldAnalyzers()).isEmpty(); assertThat(factoryBean.getFields()).isEmpty(); assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); - assertThat(factoryBean.createIndex("ExampleIndex", "/Example")).isEqualTo(mockLuceneIndex); + assertThat(factoryBean.createLuceneIndex("ExampleIndex", "/Example")).isEqualTo(mockLuceneIndex); verify(mockLuceneService, times(1)).createIndexFactory(); verify(mockLuceneIndexFactory, times(1)) @@ -158,7 +158,7 @@ public class LuceneIndexFactoryBeanUnitTests { } @Test - public void createIndexWithFieldAnalyzers() { + public void createLuceneIndexWithFieldAnalyzers() { Map fieldAnalyzers = Collections.singletonMap("fieldOne", mockAnalyzer); factoryBean.setFieldAnalyzers(fieldAnalyzers); @@ -169,7 +169,7 @@ public class LuceneIndexFactoryBeanUnitTests { assertThat(factoryBean.getFieldAnalyzers()).isEqualTo(fieldAnalyzers); assertThat(factoryBean.getFields()).isEmpty(); assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); - assertThat(factoryBean.createIndex("ExampleIndex", "/Example")).isEqualTo(mockLuceneIndex); + assertThat(factoryBean.createLuceneIndex("ExampleIndex", "/Example")).isEqualTo(mockLuceneIndex); verify(mockLuceneService, times(1)).createIndexFactory(); verify(mockLuceneIndexFactory, times(1)).setFields(eq(fieldAnalyzers)); @@ -180,7 +180,7 @@ public class LuceneIndexFactoryBeanUnitTests { } @Test - public void createIndexWithTargetedFields() { + public void createLuceneIndexWithTargetedFields() { factoryBean.setFields("fieldOne", "fieldTwo"); factoryBean.setLuceneService(mockLuceneService); @@ -189,7 +189,7 @@ public class LuceneIndexFactoryBeanUnitTests { assertThat(factoryBean.getFieldAnalyzers()).isEmpty(); assertThat(factoryBean.getFields()).containsAll(Arrays.asList("fieldOne", "fieldTwo")); assertThat(factoryBean.getLuceneService()).isSameAs(mockLuceneService); - assertThat(factoryBean.createIndex("ExampleIndex", "/Example")).isEqualTo(mockLuceneIndex); + assertThat(factoryBean.createLuceneIndex("ExampleIndex", "/Example")).isEqualTo(mockLuceneIndex); verify(mockLuceneService, times(1)).createIndexFactory(); verify(mockLuceneIndexFactory, times(1)) diff --git a/src/test/java/org/springframework/data/gemfire/server/CacheServerFactoryBeanTest.java b/src/test/java/org/springframework/data/gemfire/server/CacheServerFactoryBeanTest.java index 8a21991d..e83753c6 100644 --- a/src/test/java/org/springframework/data/gemfire/server/CacheServerFactoryBeanTest.java +++ b/src/test/java/org/springframework/data/gemfire/server/CacheServerFactoryBeanTest.java @@ -44,11 +44,12 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; /** - * The CacheServerFactoryBeanTest class is a test suite of test cases testing the contract and functionality - * of the CacheServerFactoryBean class. + * Unit tests for {@link CacheServerFactoryBean}. * * @author John Blum * @see org.junit.Test + * @see org.apache.geode.cache.Cache + * @see org.apache.geode.cache.server.CacheServer * @see org.springframework.data.gemfire.server.CacheServerFactoryBean * @since 1.6.0 */ @@ -146,7 +147,7 @@ public class CacheServerFactoryBeanTest { new CacheServerFactoryBean().afterPropertiesSet(); } catch (IllegalArgumentException expected) { - assertEquals("A GemFire Cache is required.", expected.getMessage()); + assertEquals("Cache is required", expected.getMessage()); throw expected; } } @@ -228,5 +229,4 @@ public class CacheServerFactoryBeanTest { assertFalse(factoryBean.isRunning()); assertTrue(called.get()); } - } diff --git a/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java b/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java new file mode 100644 index 00000000..1e600e74 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/AbstractFactoryBeanSupportUnitTests.java @@ -0,0 +1,168 @@ +/* + * Copyright 2017 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.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.commons.logging.Log; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.BeanFactory; + +/** + * Unit tests for {@link AbstractFactoryBeanSupport}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.Spy + * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport + * @since 1.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class AbstractFactoryBeanSupportUnitTests { + + @Mock + private Log mockLog; + + @Spy + private TestFactoryBeanSupport factoryBeanSupport; + + @Before + public void setup() { + when(factoryBeanSupport.getLog()).thenReturn(mockLog); + } + + @Test + public void setAndGetBeanClassLoader() { + assertThat(factoryBeanSupport.getBeanClassLoader()).isNull(); + + ClassLoader mockClassLoader = mock(ClassLoader.class); + + factoryBeanSupport.setBeanClassLoader(mockClassLoader); + + assertThat(factoryBeanSupport.getBeanClassLoader()).isSameAs(mockClassLoader); + + ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); + + factoryBeanSupport.setBeanClassLoader(systemClassLoader); + + assertThat(factoryBeanSupport.getBeanClassLoader()).isSameAs(systemClassLoader); + + factoryBeanSupport.setBeanClassLoader(null); + + assertThat(factoryBeanSupport.getBeanClassLoader()).isNull(); + } + + @Test + public void setAndGetBeanFactory() { + assertThat(factoryBeanSupport.getBeanFactory()).isNull(); + + BeanFactory mockBeanFactory = mock(BeanFactory.class); + + factoryBeanSupport.setBeanFactory(mockBeanFactory); + + assertThat(factoryBeanSupport.getBeanFactory()).isSameAs(mockBeanFactory); + + factoryBeanSupport.setBeanFactory(null); + + assertThat(factoryBeanSupport.getBeanFactory()).isNull(); + } + + @Test + public void setAndGetBeanName() { + assertThat(factoryBeanSupport.getBeanName()).isNullOrEmpty(); + + factoryBeanSupport.setBeanName("test"); + + assertThat(factoryBeanSupport.getBeanName()).isEqualTo("test"); + + factoryBeanSupport.setBeanName(null); + + assertThat(factoryBeanSupport.getBeanName()).isNullOrEmpty(); + } + + @Test + public void isSingletonDefaultsToTrue() { + assertThat(factoryBeanSupport.isSingleton()).isTrue(); + } + + @Test + public void logsDebugWhenDebugIsEnabled() { + when(mockLog.isDebugEnabled()).thenReturn(true); + + factoryBeanSupport.logDebug("%s log test", "debug"); + + verify(mockLog, times(1)).isDebugEnabled(); + verify(mockLog, times(1)).debug(eq("debug log test")); + } + + @Test + public void logsInfoWhenInfoIsEnabled() { + when(mockLog.isInfoEnabled()).thenReturn(true); + + factoryBeanSupport.logInfo("%s log test", "info"); + + verify(mockLog, times(1)).isInfoEnabled(); + verify(mockLog, times(1)).info(eq("info log test")); + } + + @Test + public void suppressesDebugLoggingWhenDebugIsDisabled() { + when(mockLog.isDebugEnabled()).thenReturn(false); + + factoryBeanSupport.logDebug(() -> "test"); + + verify(mockLog, times(1)).isDebugEnabled(); + verify(mockLog, never()).debug(any()); + } + + @Test + public void suppressesInfoLoggingWhenInfoIsDisabled() { + when(mockLog.isInfoEnabled()).thenReturn(false); + + factoryBeanSupport.logInfo(() -> "test"); + + verify(mockLog, times(1)).isInfoEnabled(); + verify(mockLog, never()).info(any()); + } + + private static class TestFactoryBeanSupport extends AbstractFactoryBeanSupport { + + @Override + public T getObject() throws Exception { + return null; + } + + @Override + public Class getObjectType() { + return Object.class; + } + } +} diff --git a/src/test/java/org/springframework/data/gemfire/test/MockCacheFactoryBean.java b/src/test/java/org/springframework/data/gemfire/test/MockCacheFactoryBean.java index 277475be..5a1580d7 100644 --- a/src/test/java/org/springframework/data/gemfire/test/MockCacheFactoryBean.java +++ b/src/test/java/org/springframework/data/gemfire/test/MockCacheFactoryBean.java @@ -13,6 +13,8 @@ package org.springframework.data.gemfire.test; +import java.util.Optional; + import org.apache.geode.cache.GemFireCache; import org.springframework.data.gemfire.CacheFactoryBean; @@ -27,36 +29,39 @@ public class MockCacheFactoryBean extends CacheFactoryBean { } public MockCacheFactoryBean(CacheFactoryBean cacheFactoryBean) { + setUseBeanFactoryLocator(false); - if (cacheFactoryBean != null) { - setBeanClassLoader(cacheFactoryBean.getBeanClassLoader()); - setBeanFactory(cacheFactoryBean.getBeanFactory()); - setBeanName(cacheFactoryBean.getBeanName()); - setCacheXml(cacheFactoryBean.getCacheXml()); - setPhase(cacheFactoryBean.getPhase()); - setProperties(cacheFactoryBean.getProperties()); - setClose(cacheFactoryBean.getClose()); - setCopyOnRead(cacheFactoryBean.getCopyOnRead()); - setCriticalHeapPercentage(cacheFactoryBean.getCriticalHeapPercentage()); - setDynamicRegionSupport(cacheFactoryBean.getDynamicRegionSupport()); - setEnableAutoReconnect(cacheFactoryBean.getEnableAutoReconnect()); - setEvictionHeapPercentage(cacheFactoryBean.getEvictionHeapPercentage()); - setGatewayConflictResolver(cacheFactoryBean.getGatewayConflictResolver()); - setJndiDataSources(cacheFactoryBean.getJndiDataSources()); - setLockLease(cacheFactoryBean.getLockLease()); - setLockTimeout(cacheFactoryBean.getLockTimeout()); - setMessageSyncInterval(cacheFactoryBean.getMessageSyncInterval()); - setPdxDiskStoreName(cacheFactoryBean.getPdxDiskStoreName()); - setPdxIgnoreUnreadFields(cacheFactoryBean.getPdxIgnoreUnreadFields()); - setPdxPersistent(cacheFactoryBean.getPdxPersistent()); - setPdxReadSerialized(cacheFactoryBean.getPdxReadSerialized()); - setPdxSerializer(cacheFactoryBean.getPdxSerializer()); - setSearchTimeout(cacheFactoryBean.getSearchTimeout()); - setTransactionListeners(cacheFactoryBean.getTransactionListeners()); - setTransactionWriter(cacheFactoryBean.getTransactionWriter()); - setUseBeanFactoryLocator(cacheFactoryBean.isUseBeanFactoryLocator()); - } + Optional.ofNullable(cacheFactoryBean).ifPresent(it -> { + setBeanClassLoader(it.getBeanClassLoader()); + setBeanFactory(it.getBeanFactory()); + setBeanName(it.getBeanName()); + setCacheXml(it.getCacheXml()); + setPhase(it.getPhase()); + setProperties(it.getProperties()); + setClose(it.isClose()); + setCopyOnRead(it.getCopyOnRead()); + setCriticalHeapPercentage(it.getCriticalHeapPercentage()); + setDynamicRegionSupport(it.getDynamicRegionSupport()); + setEnableAutoReconnect(it.getEnableAutoReconnect()); + setEvictionHeapPercentage(it.getEvictionHeapPercentage()); + setGatewayConflictResolver(it.getGatewayConflictResolver()); + setJndiDataSources(it.getJndiDataSources()); + setLockLease(it.getLockLease()); + setLockTimeout(it.getLockTimeout()); + setMessageSyncInterval(it.getMessageSyncInterval()); + setPdxDiskStoreName(it.getPdxDiskStoreName()); + setPdxIgnoreUnreadFields(it.getPdxIgnoreUnreadFields()); + setPdxPersistent(it.getPdxPersistent()); + setPdxReadSerialized(it.getPdxReadSerialized()); + setPdxSerializer(it.getPdxSerializer()); + setSearchTimeout(it.getSearchTimeout()); + setTransactionListeners(it.getTransactionListeners()); + setTransactionWriter(it.getTransactionWriter()); + setUseBeanFactoryLocator(it.isUseBeanFactoryLocator()); + }); + + applyPeerCacheConfigurers(cacheFactoryBean.getCompositePeerCacheConfigurer()); } @Override @@ -66,5 +71,4 @@ public class MockCacheFactoryBean extends CacheFactoryBean { stubCache.setProperties(getProperties()); return (T) stubCache; } - } diff --git a/src/test/java/org/springframework/data/gemfire/test/MockClientCacheFactoryBean.java b/src/test/java/org/springframework/data/gemfire/test/MockClientCacheFactoryBean.java index e88f4351..95f427ef 100644 --- a/src/test/java/org/springframework/data/gemfire/test/MockClientCacheFactoryBean.java +++ b/src/test/java/org/springframework/data/gemfire/test/MockClientCacheFactoryBean.java @@ -12,6 +12,8 @@ */ package org.springframework.data.gemfire.test; +import java.util.Optional; + import org.apache.geode.cache.GemFireCache; import org.springframework.data.gemfire.client.ClientCacheFactoryBean; @@ -22,38 +24,41 @@ import org.springframework.data.gemfire.client.ClientCacheFactoryBean; public class MockClientCacheFactoryBean extends ClientCacheFactoryBean { public MockClientCacheFactoryBean(ClientCacheFactoryBean clientCacheFactoryBean) { + setUseBeanFactoryLocator(false); - if (clientCacheFactoryBean != null) { - this.beanFactoryLocator = clientCacheFactoryBean.getBeanFactoryLocator(); - setBeanClassLoader(clientCacheFactoryBean.getBeanClassLoader()); - setBeanFactory(clientCacheFactoryBean.getBeanFactory()); - setBeanName(clientCacheFactoryBean.getBeanName()); - setCacheXml(clientCacheFactoryBean.getCacheXml()); - setCopyOnRead(clientCacheFactoryBean.getCopyOnRead()); - setCriticalHeapPercentage(clientCacheFactoryBean.getCriticalHeapPercentage()); - setDurableClientId(clientCacheFactoryBean.getDurableClientId()); - setDurableClientTimeout(clientCacheFactoryBean.getDurableClientTimeout()); - setDynamicRegionSupport(clientCacheFactoryBean.getDynamicRegionSupport()); - setEvictionHeapPercentage(clientCacheFactoryBean.getEvictionHeapPercentage()); - setGatewayConflictResolver(clientCacheFactoryBean.getGatewayConflictResolver()); - setJndiDataSources(clientCacheFactoryBean.getJndiDataSources()); - setKeepAlive(clientCacheFactoryBean.isKeepAlive()); - setLockLease(clientCacheFactoryBean.getLockLease()); - setLockTimeout(clientCacheFactoryBean.getLockTimeout()); - setMessageSyncInterval(clientCacheFactoryBean.getMessageSyncInterval()); - setPdxDiskStoreName(clientCacheFactoryBean.getPdxDiskStoreName()); - setPdxIgnoreUnreadFields(clientCacheFactoryBean.getPdxIgnoreUnreadFields()); - setPdxPersistent(clientCacheFactoryBean.getPdxPersistent()); - setPdxReadSerialized(clientCacheFactoryBean.getPdxReadSerialized()); - setPdxSerializer(clientCacheFactoryBean.getPdxSerializer()); - setPoolName(clientCacheFactoryBean.getPoolName()); - setProperties(clientCacheFactoryBean.getProperties()); - setReadyForEvents(clientCacheFactoryBean.getReadyForEvents()); - setSearchTimeout(clientCacheFactoryBean.getSearchTimeout()); - setTransactionListeners(clientCacheFactoryBean.getTransactionListeners()); - setTransactionWriter(clientCacheFactoryBean.getTransactionWriter()); - } + Optional.ofNullable(clientCacheFactoryBean).ifPresent(it -> { + this.beanFactoryLocator = it.getBeanFactoryLocator(); + setBeanClassLoader(it.getBeanClassLoader()); + setBeanFactory(it.getBeanFactory()); + setBeanName(it.getBeanName()); + setCacheXml(it.getCacheXml()); + setCopyOnRead(it.getCopyOnRead()); + setCriticalHeapPercentage(it.getCriticalHeapPercentage()); + setDurableClientId(it.getDurableClientId()); + setDurableClientTimeout(it.getDurableClientTimeout()); + setDynamicRegionSupport(it.getDynamicRegionSupport()); + setEvictionHeapPercentage(it.getEvictionHeapPercentage()); + setGatewayConflictResolver(it.getGatewayConflictResolver()); + setJndiDataSources(it.getJndiDataSources()); + setKeepAlive(it.isKeepAlive()); + setLockLease(it.getLockLease()); + setLockTimeout(it.getLockTimeout()); + setMessageSyncInterval(it.getMessageSyncInterval()); + setPdxDiskStoreName(it.getPdxDiskStoreName()); + setPdxIgnoreUnreadFields(it.getPdxIgnoreUnreadFields()); + setPdxPersistent(it.getPdxPersistent()); + setPdxReadSerialized(it.getPdxReadSerialized()); + setPdxSerializer(it.getPdxSerializer()); + setPoolName(it.getPoolName()); + setProperties(it.getProperties()); + setReadyForEvents(it.getReadyForEvents()); + setSearchTimeout(it.getSearchTimeout()); + setTransactionListeners(it.getTransactionListeners()); + setTransactionWriter(it.getTransactionWriter()); + }); + + applyClientCacheConfigurers(clientCacheFactoryBean.getCompositeClientCacheConfigurer()); } @SuppressWarnings("unchecked") @@ -62,6 +67,4 @@ public class MockClientCacheFactoryBean extends ClientCacheFactoryBean { stubCache.setProperties(getProperties()); return (T) stubCache; } - - } diff --git a/src/test/resources/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest-context.xml b/src/test/resources/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest-context.xml index 029a0978..79678131 100644 --- a/src/test/resources/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest-context.xml +++ b/src/test/resources/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest-context.xml @@ -16,18 +16,17 @@ - warning + config - - - + - + - - - + @@ -38,6 +37,10 @@ + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest-server-context.xml b/src/test/resources/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest-server-context.xml index f77d73df..3e894af6 100644 --- a/src/test/resources/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest-server-context.xml +++ b/src/test/resources/org/springframework/data/gemfire/function/ClientCacheFunctionExecutionWithPdxIntegrationTest-server-context.xml @@ -15,14 +15,17 @@ SpringGemFireServerFunctionCreationWithPdx 0 - warning + config - + - + - +