/** * Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org> * * 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.onebusaway.container.spring.ehcache; import java.io.IOException; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheException; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Ehcache; import net.sf.ehcache.config.CacheConfiguration; import net.sf.ehcache.config.PersistenceConfiguration; import net.sf.ehcache.config.TerracottaConfiguration; import net.sf.ehcache.constructs.blocking.BlockingCache; import net.sf.ehcache.constructs.blocking.CacheEntryFactory; import net.sf.ehcache.constructs.blocking.SelfPopulatingCache; import net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory; import net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache; import net.sf.ehcache.store.MemoryStoreEvictionPolicy; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; /** * A Spring {@link FactoryBean} for programmatically creating an EhCache * {@link Cache} with standard configuration options, but also progrmattic * Terracotta configuration. * * A special note about Terracotta config. While you can create all your * Terracotta-enabled caches programatically using this FactoryBean, you need to * have at least one Terracotta-enabled cache created the old fashioned way * (through an {@code ehcache.xml} resource config) so that the * {@link CacheManager} will properly enable Terracotta support. * * @author bdferris * */ public class EhCacheFactoryBean implements FactoryBean<Ehcache>, BeanNameAware, InitializingBean { protected final Log logger = LogFactory.getLog(getClass()); private CacheManager cacheManager; private String cacheName; private int maxElementsInMemory = 10000; private int maxElementsOnDisk = 10000000; private MemoryStoreEvictionPolicy memoryStoreEvictionPolicy = MemoryStoreEvictionPolicy.LRU; private boolean overflowToDisk = true; private boolean eternal = false; private int timeToLive = 60 * 5; private int timeToIdle = 120; private boolean diskPersistent = false; private int diskExpiryThreadIntervalSeconds = 60 * 5; private boolean blocking = false; private boolean terracottaClustered = false; private CacheEntryFactory cacheEntryFactory; private String beanName; private Ehcache cache; /** * Set a CacheManager from which to retrieve a named Cache instance. By * default, <code>CacheManager.getInstance()</code> will be called. * <p> * Note that in particular for persistent caches, it is advisable to properly * handle the shutdown of the CacheManager: Set up a separate * EhCacheManagerFactoryBean and pass a reference to this bean property. * <p> * A separate EhCacheManagerFactoryBean is also necessary for loading EHCache * configuration from a non-default config location. * * @see EhCacheManagerFactoryBean * @see net.sf.ehcache.CacheManager#getInstance */ public void setCacheManager(CacheManager cacheManager) { this.cacheManager = cacheManager; } /** * Set a name for which to retrieve or create a cache instance. Default is the * bean name of this EhCacheFactoryBean. */ public void setCacheName(String cacheName) { this.cacheName = cacheName; } /** * Specify the maximum number of cached objects in memory. Default is 10000 * elements. */ public void setMaxElementsInMemory(int maxElementsInMemory) { this.maxElementsInMemory = maxElementsInMemory; } /** * Specify the maximum number of cached objects on disk. Default is 10000000 * elements. */ public void setMaxElementsOnDisk(int maxElementsOnDisk) { this.maxElementsOnDisk = maxElementsOnDisk; } /** * Set the memory style eviction policy for this cache. Supported values are * "LRU", "LFU" and "FIFO", according to the constants defined in EHCache's * MemoryStoreEvictionPolicy class. Default is "LRU". */ public void setMemoryStoreEvictionPolicy( MemoryStoreEvictionPolicy memoryStoreEvictionPolicy) { Assert.notNull(memoryStoreEvictionPolicy, "memoryStoreEvictionPolicy must not be null"); this.memoryStoreEvictionPolicy = memoryStoreEvictionPolicy; } /** * Set whether elements can overflow to disk when the in-memory cache has * reached the maximum size limit. Default is "true". */ public void setOverflowToDisk(boolean overflowToDisk) { this.overflowToDisk = overflowToDisk; } /** * Set whether elements are considered as eternal. If "true", timeouts are * ignored and the element is never expired. Default is "false". */ public void setEternal(boolean eternal) { this.eternal = eternal; } /** * Set t he time in seconds to live for an element before it expires, i.e. the * maximum time between creation time and when an element expires. It is only * used if the element is not eternal. Default is 300 seconds. */ public void setTimeToLive(int timeToLive) { this.timeToLive = timeToLive; } /** * Set the time in seconds to idle for an element before it expires, that is, * the maximum amount of time between accesses before an element expires. This * is only used if the element is not eternal. Default is 120 seconds. */ public void setTimeToIdle(int timeToIdle) { this.timeToIdle = timeToIdle; } /** * Set whether the disk store persists between restarts of the Virtual * Machine. The default is "false". */ public void setDiskPersistent(boolean diskPersistent) { this.diskPersistent = diskPersistent; } /** * Set the number of seconds between runs of the disk expiry thread. The * default is 300 seconds. */ public void setDiskExpiryThreadIntervalSeconds( int diskExpiryThreadIntervalSeconds) { this.diskExpiryThreadIntervalSeconds = diskExpiryThreadIntervalSeconds; } /** * Set whether to use a blocking cache that lets read attempts block until the * requested element is created. * <p> * If you intend to build a self-populating blocking cache, consider * specifying a {@link #setCacheEntryFactory CacheEntryFactory}. * * @see net.sf.ehcache.constructs.blocking.BlockingCache * @see #setCacheEntryFactory */ public void setBlocking(boolean blocking) { this.blocking = blocking; } public void setTerracottaClustered(boolean terracottaClustered) { this.terracottaClustered = terracottaClustered; } /** * Set an EHCache {@link net.sf.ehcache.constructs.blocking.CacheEntryFactory} * to use for a self-populating cache. If such a factory is specified, the * cache will be decorated with EHCache's * {@link net.sf.ehcache.constructs.blocking.SelfPopulatingCache}. * <p> * The specified factory can be of type * {@link net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory}, which * will lead to the use of an * {@link net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache}. * <p> * Note: Any such self-populating cache is automatically a blocking cache. * * @see net.sf.ehcache.constructs.blocking.SelfPopulatingCache * @see net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache * @see net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory */ public void setCacheEntryFactory(CacheEntryFactory cacheEntryFactory) { this.cacheEntryFactory = cacheEntryFactory; } public void setBeanName(String name) { this.beanName = name; } public void afterPropertiesSet() throws CacheException, IOException { // If no CacheManager given, fetch the default. if (this.cacheManager == null) { if (logger.isDebugEnabled()) { logger.debug("Using default EHCache CacheManager for cache region '" + this.cacheName + "'"); } this.cacheManager = CacheManager.getInstance(); } // If no cache name given, use bean name as cache name. if (this.cacheName == null) { this.cacheName = this.beanName; } // Fetch cache region: If none with the given name exists, // create one on the fly. Ehcache rawCache = null; if (this.cacheManager.cacheExists(this.cacheName)) { if (logger.isDebugEnabled()) { logger.debug("Using existing EHCache cache region '" + this.cacheName + "'"); } rawCache = this.cacheManager.getEhcache(this.cacheName); } else { if (logger.isDebugEnabled()) { logger.debug("Creating new EHCache cache region '" + this.cacheName + "'"); } rawCache = createCache(); this.cacheManager.addCache(rawCache); } // Decorate cache if necessary. Ehcache decoratedCache = decorateCache(rawCache); if (decoratedCache != rawCache) { this.cacheManager.replaceCacheWithDecoratedCache(rawCache, decoratedCache); } this.cache = decoratedCache; } /** * Create a raw Cache object based on the configuration of this FactoryBean. */ private Cache createCache() { CacheConfiguration config = new CacheConfiguration(this.cacheName, this.maxElementsInMemory); config.setMemoryStoreEvictionPolicyFromObject(this.memoryStoreEvictionPolicy); config.setEternal(this.eternal); config.setTimeToLiveSeconds(this.timeToLive); config.setTimeToIdleSeconds(this.timeToIdle); PersistenceConfiguration pc = new PersistenceConfiguration(); if(this.diskPersistent) pc.strategy(PersistenceConfiguration.Strategy.LOCALRESTARTABLE); else if(this.overflowToDisk) pc.strategy(PersistenceConfiguration.Strategy.LOCALTEMPSWAP); else pc.strategy(PersistenceConfiguration.Strategy.NONE); config.setDiskExpiryThreadIntervalSeconds(this.diskExpiryThreadIntervalSeconds); config.setMaxElementsOnDisk(this.maxElementsOnDisk); if (this.terracottaClustered) { TerracottaConfiguration tcConfig = new TerracottaConfiguration(); tcConfig.setClustered(true); config.terracotta(tcConfig); } return new Cache(config); } /** * Decorate the given Cache, if necessary. * * @param cache the raw Cache object, based on the configuration of this * FactoryBean * @return the (potentially decorated) cache object to be registered with the * CacheManager */ protected Ehcache decorateCache(Ehcache cache) { if (this.cacheEntryFactory != null) { if (this.cacheEntryFactory instanceof UpdatingCacheEntryFactory) { return new UpdatingSelfPopulatingCache(cache, (UpdatingCacheEntryFactory) this.cacheEntryFactory); } else { return new SelfPopulatingCache(cache, this.cacheEntryFactory); } } if (this.blocking) { return new BlockingCache(cache); } return cache; } public Ehcache getObject() { return this.cache; } public Class<?> getObjectType() { return (this.cache != null ? this.cache.getClass() : Ehcache.class); } public boolean isSingleton() { return true; } }