/* * Copyright 2010-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.gemfire.client; import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; 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; import org.apache.geode.cache.DataPolicy; import org.apache.geode.cache.EvictionAttributes; import org.apache.geode.cache.GemFireCache; 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.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.xml.GemfireConstants; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Spring {@link FactoryBean} used to create a GemFire client cache {@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 * @see org.apache.geode.cache.Region * @see org.apache.geode.cache.RegionAttributes * @see org.apache.geode.cache.client.ClientCache * @see org.apache.geode.cache.client.ClientRegionFactory * @see org.apache.geode.cache.client.ClientRegionShortcut * @see org.apache.geode.cache.client.Pool */ @SuppressWarnings("unused") public class ClientRegionFactoryBean<K, V> extends RegionLookupFactoryBean<K, V> implements BeanFactoryAware, DisposableBean { private static final Log log = LogFactory.getLog(ClientRegionFactoryBean.class); private boolean close = false; private boolean destroy = false; private BeanFactory beanFactory; private Boolean persistent; private CacheListener<K, V>[] cacheListeners; private CacheLoader<K, V> cacheLoader; private CacheWriter<K, V> cacheWriter; private Class<K> keyConstraint; private Class<V> valueConstraint; private ClientRegionShortcut shortcut = null; private DataPolicy dataPolicy; private EvictionAttributes evictionAttributes; private Interest<K>[] interests; private RegionAttributes<K, V> attributes; private Resource snapshot; private String diskStoreName; private String poolName; /** * @inheritDoc */ @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); postProcess(getRegion()); } /** * @inheritDoc */ @Override protected Region<K, V> lookupRegion(GemFireCache cache, String regionName) throws Exception { Assert.isTrue(GemfireUtils.isClient(cache), "A ClientCache is required to create a client Region"); ClientRegionFactory<K, V> clientRegionFactory = ((ClientCache) cache).createClientRegionFactory(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<K, V> create(ClientRegionFactory<K, V> clientRegionFactory, String regionName) { return (getParent() != null ? clientRegionFactory.createSubregion(getParent(), regionName) : clientRegionFactory.create(regionName)); } /* (non-Javadoc) */ private Region<K, V> logCreateRegionEvent(Region<K, V> 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())); } } return region; } /** * Resolves the {@link ClientRegionShortcut} used to configure the data policy of the client {@link Region}. * * @return a {@link ClientRegionShortcut} used to configure the data policy of the client {@link Region}. * @see org.apache.geode.cache.client.ClientRegionShortcut */ ClientRegionShortcut resolveClientRegionShortcut() { ClientRegionShortcut resolvedShortcut = this.shortcut; if (resolvedShortcut == null) { if (this.dataPolicy != null) { assertDataPolicyAndPersistentAttributeAreCompatible(this.dataPolicy); if (DataPolicy.EMPTY.equals(this.dataPolicy)) { resolvedShortcut = ClientRegionShortcut.PROXY; } else if (DataPolicy.NORMAL.equals(this.dataPolicy)) { resolvedShortcut = ClientRegionShortcut.CACHING_PROXY; } else if (DataPolicy.PERSISTENT_REPLICATE.equals(this.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)); } } else { resolvedShortcut = (isPersistent() ? ClientRegionShortcut.LOCAL_PERSISTENT : ClientRegionShortcut.LOCAL); } } // 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<K, V> setAttributes(ClientRegionFactory<K, V> clientRegionFactory) { RegionAttributes<K, V> 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<K, V> addCacheListeners(ClientRegionFactory<K, V> clientRegionFactory) { for (CacheListener<K, V> cacheListener : this.<K, V>attributesCacheListeners()) { clientRegionFactory.addCacheListener(cacheListener); } for (CacheListener<K, V> cacheListener : nullSafeArray(this.cacheListeners, CacheListener.class)) { clientRegionFactory.addCacheListener(cacheListener); } return clientRegionFactory; } /* (non-Javadoc) */ @SuppressWarnings("unchecked") private <K, V> CacheListener<K, V>[] attributesCacheListeners() { CacheListener[] cacheListeners = (this.attributes != null ? this.attributes.getCacheListeners() : null); return nullSafeArray(cacheListeners, CacheListener.class); } /* (non-Javadoc) */ private ClientRegionFactory<K, V> setDiskStoreName(ClientRegionFactory<K, V> clientRegionFactory) { if (StringUtils.hasText(this.diskStoreName)) { clientRegionFactory.setDiskStoreName(this.diskStoreName); } return clientRegionFactory; } /* (non-Javadoc) */ private ClientRegionFactory<K, V> setEvictionAttributes(ClientRegionFactory<K, V> clientRegionFactory) { if (this.evictionAttributes != null) { clientRegionFactory.setEvictionAttributes(this.evictionAttributes); } return clientRegionFactory; } /* (non-Javadoc) */ private ClientRegionFactory<K, V> setPoolName(ClientRegionFactory<K, V> 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); } return poolName; } /* (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); } } catch (BeansException ignore) { log.warn(ignore.getMessage()); } return poolName; } /* (non-Javadoc) */ protected void postProcess(Region<K, V> region) throws Exception { loadSnapshot(region); registerInterests(region); setCacheLoader(region); setCacheWriter(region); } /* (non-Javadoc) */ private Region<K, V> loadSnapshot(Region<K, V> region) throws Exception { if (snapshot != null) { region.loadSnapshot(snapshot.getInputStream()); } return region; } /* (non-Javadoc) */ @SuppressWarnings("unchecked") private Region<K, V> registerInterests(Region<K, V> region) { for (Interest<K> interest : nullSafeArray(interests, Interest.class)) { 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()); } } return region; } /* (non-Javadoc) */ private Region<K, V> setCacheLoader(Region<K, V> region) { if (cacheLoader != null) { region.getAttributesMutator().setCacheLoader(this.cacheLoader); } return region; } /* (non-Javadoc) */ private Region<K, V> setCacheWriter(Region<K, V> region) { if (cacheWriter != null) { region.getAttributesMutator().setCacheWriter(this.cacheWriter); } return region; } /** * @inheritDoc */ @Override public void destroy() throws Exception { Region<K, V> region = getObject(); if (region != null) { if (close) { if (!region.getRegionService().isClosed()) { try { region.close(); } catch (Exception ignore) { } } } if (destroy) { region.destroyRegion(); } } } /** * Sets the region attributes used for the region used by this factory. * Allows maximum control in specifying the region settings. Used only when * a new region is created. Note that using this method allows for advanced * customization of the region - while it provides a lot of flexibility, * note that it's quite easy to create misconfigured regions (especially in * a client/server scenario). * * @param attributes the attributes to set on a newly created region */ public void setAttributes(RegionAttributes<K, V> attributes) { 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 * through {@link #setAttributes(RegionAttributes)}. * * @param cacheListeners the cacheListeners to set on a newly created region */ public void setCacheListeners(CacheListener<K, V>[] cacheListeners) { this.cacheListeners = cacheListeners; } /** * Sets the CacheLoader used to load data local to the client's Region on cache misses. * * @param cacheLoader a GemFire CacheLoader used to load data into the client Region. * @see org.apache.geode.cache.CacheLoader */ public void setCacheLoader(CacheLoader<K, V> cacheLoader) { this.cacheLoader = cacheLoader; } /** * Sets the CacheWriter used to perform a synchronous write-behind when data is put into the client's Region. * * @param cacheWriter the GemFire CacheWriter used to perform synchronous write-behinds on put ops. * @see org.apache.geode.cache.CacheWriter */ public void setCacheWriter(CacheWriter<K, V> cacheWriter) { this.cacheWriter = cacheWriter; } /** * Sets the Data Policy. Used only when a new Region is created. * * @param dataPolicy the client Region's Data Policy. * @see org.apache.geode.cache.DataPolicy */ public void setDataPolicy(DataPolicy dataPolicy) { this.dataPolicy = dataPolicy; } /** * An alternate way to set the Data Policy, using the String name of the enumerated value. * * @param dataPolicyName the enumerated value String name of the Data Policy. * @see org.apache.geode.cache.DataPolicy * @see #setDataPolicy(org.apache.geode.cache.DataPolicy) * @deprecated use setDataPolicy(:DataPolicy) instead. */ @Deprecated public void setDataPolicyName(String dataPolicyName) { DataPolicy resolvedDataPolicy = new DataPolicyConverter().convert(dataPolicyName); Assert.notNull(resolvedDataPolicy, String.format("Data Policy '%1$s' is invalid.", dataPolicyName)); setDataPolicy(resolvedDataPolicy); } /* (non-Javadoc) */ final boolean isDestroy() { return destroy; } /** * Indicates whether the region referred by this factory bean will be * destroyed on shutdown (default false). Note: destroy and close are * mutually exclusive. Enabling one will automatically disable the other. * * @param destroy whether or not to destroy the region * @see #setClose(boolean) */ public void setDestroy(boolean destroy) { this.destroy = destroy; this.close = (this.close && !destroy); // retain previous value iff destroy is false; } /** * Sets the name of disk store to use for overflow and persistence * * @param diskStoreName a String specifying the 'name' of the client Region Disk Store. */ public void setDiskStoreName(String diskStoreName) { this.diskStoreName = diskStoreName; } public void setEvictionAttributes(EvictionAttributes evictionAttributes) { this.evictionAttributes = evictionAttributes; } /** * Set the interests for this client region. Both key and regex interest are * supported. * * @param interests the interests to set */ public void setInterests(Interest<K>[] interests) { this.interests = interests; } /* (non-Javadoc) */ Interest<K>[] getInterests() { return this.interests; } public void setKeyConstraint(Class<K> keyConstraint) { this.keyConstraint = keyConstraint; } protected boolean isPersistentUnspecified() { return (persistent == null); } protected boolean isPersistent() { return Boolean.TRUE.equals(persistent); } protected boolean isNotPersistent() { return Boolean.FALSE.equals(persistent); } public void setPersistent(final boolean persistent) { this.persistent = persistent; } /** * Sets the {@link Pool} used by this client {@link Region}. * * @param pool GemFire client {@link Pool}. * @see org.apache.geode.cache.client.Pool */ public void setPool(Pool pool) { Assert.notNull(pool, "Pool cannot be null"); setPoolName(pool.getName()); } /** * Sets the {@link Pool} name used by this client {@link Region}. * * @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; } /** * Initializes the client {@link Region} using a GemFire {@link ClientRegionShortcut}. * * @param shortcut {@link ClientRegionShortcut} used to initialize this client {@link Region}. */ public void setShortcut(ClientRegionShortcut shortcut) { this.shortcut = shortcut; } /** * Specifies the data snapshots used for loading a newly <i>created</i> {@link Region}. * The snapshot will be used <i>only</i> 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<V> valueConstraint) { this.valueConstraint = valueConstraint; } }