001/*
002 * Copyright 2002-2014 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.cache.ehcache;
018
019import java.lang.reflect.Method;
020import java.util.Set;
021
022import net.sf.ehcache.Cache;
023import net.sf.ehcache.CacheException;
024import net.sf.ehcache.CacheManager;
025import net.sf.ehcache.Ehcache;
026import net.sf.ehcache.bootstrap.BootstrapCacheLoader;
027import net.sf.ehcache.config.CacheConfiguration;
028import net.sf.ehcache.constructs.blocking.BlockingCache;
029import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
030import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
031import net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory;
032import net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache;
033import net.sf.ehcache.event.CacheEventListener;
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036
037import org.springframework.beans.factory.BeanNameAware;
038import org.springframework.beans.factory.FactoryBean;
039import org.springframework.beans.factory.InitializingBean;
040import org.springframework.util.ClassUtils;
041import org.springframework.util.ReflectionUtils;
042
043/**
044 * {@link FactoryBean} that creates a named EhCache {@link net.sf.ehcache.Cache} instance
045 * (or a decorator that implements the {@link net.sf.ehcache.Ehcache} interface),
046 * representing a cache region within an EhCache {@link net.sf.ehcache.CacheManager}.
047 *
048 * <p>If the specified named cache is not configured in the cache configuration descriptor,
049 * this FactoryBean will construct an instance of a Cache with the provided name and the
050 * specified cache properties and add it to the CacheManager for later retrieval. If some
051 * or all properties are not set at configuration time, this FactoryBean will use defaults.
052 *
053 * <p>Note: If the named Cache instance is found, the properties will be ignored and the
054 * Cache instance will be retrieved from the CacheManager.
055 *
056 * <p>Note: As of Spring 4.1, Spring's EhCache support requires EhCache 2.5 or higher.
057 *
058 * @author Juergen Hoeller
059 * @author Dmitriy Kopylenko
060 * @since 1.1.1
061 * @see #setCacheManager
062 * @see EhCacheManagerFactoryBean
063 * @see net.sf.ehcache.Cache
064 */
065public class EhCacheFactoryBean extends CacheConfiguration implements FactoryBean<Ehcache>, BeanNameAware, InitializingBean {
066
067        // EhCache's setStatisticsEnabled(boolean) available? Not anymore as of EhCache 2.7...
068        private static final Method setStatisticsEnabledMethod =
069                        ClassUtils.getMethodIfAvailable(Ehcache.class, "setStatisticsEnabled", boolean.class);
070
071        // EhCache's setSampledStatisticsEnabled(boolean) available? Not anymore as of EhCache 2.7...
072        private static final Method setSampledStatisticsEnabledMethod =
073                        ClassUtils.getMethodIfAvailable(Ehcache.class, "setSampledStatisticsEnabled", boolean.class);
074
075
076        protected final Log logger = LogFactory.getLog(getClass());
077
078        private CacheManager cacheManager;
079
080        private boolean blocking = false;
081
082        private CacheEntryFactory cacheEntryFactory;
083
084        private BootstrapCacheLoader bootstrapCacheLoader;
085
086        private Set<CacheEventListener> cacheEventListeners;
087
088        private boolean statisticsEnabled = false;
089
090        private boolean sampledStatisticsEnabled = false;
091
092        private boolean disabled = false;
093
094        private String beanName;
095
096        private Ehcache cache;
097
098
099        @SuppressWarnings("deprecation")
100        public EhCacheFactoryBean() {
101                setMaxEntriesLocalHeap(10000);
102                setMaxElementsOnDisk(10000000);
103                setTimeToLiveSeconds(120);
104                setTimeToIdleSeconds(120);
105        }
106
107
108        /**
109         * Set a CacheManager from which to retrieve a named Cache instance.
110         * By default, {@code CacheManager.getInstance()} will be called.
111         * <p>Note that in particular for persistent caches, it is advisable to
112         * properly handle the shutdown of the CacheManager: Set up a separate
113         * EhCacheManagerFactoryBean and pass a reference to this bean property.
114         * <p>A separate EhCacheManagerFactoryBean is also necessary for loading
115         * EhCache configuration from a non-default config location.
116         * @see EhCacheManagerFactoryBean
117         * @see net.sf.ehcache.CacheManager#getInstance
118         */
119        public void setCacheManager(CacheManager cacheManager) {
120                this.cacheManager = cacheManager;
121        }
122
123        /**
124         * Set a name for which to retrieve or create a cache instance.
125         * Default is the bean name of this EhCacheFactoryBean.
126         */
127        public void setCacheName(String cacheName) {
128                setName(cacheName);
129        }
130
131        /**
132         * @see #setTimeToLiveSeconds(long)
133         */
134        public void setTimeToLive(int timeToLive) {
135                setTimeToLiveSeconds(timeToLive);
136        }
137
138        /**
139         * @see #setTimeToIdleSeconds(long)
140         */
141        public void setTimeToIdle(int timeToIdle) {
142                setTimeToIdleSeconds(timeToIdle);
143        }
144
145        /**
146         * @see #setDiskSpoolBufferSizeMB(int)
147         */
148        public void setDiskSpoolBufferSize(int diskSpoolBufferSize) {
149                setDiskSpoolBufferSizeMB(diskSpoolBufferSize);
150        }
151
152        /**
153         * Set whether to use a blocking cache that lets read attempts block
154         * until the requested element is created.
155         * <p>If you intend to build a self-populating blocking cache,
156         * consider specifying a {@link #setCacheEntryFactory CacheEntryFactory}.
157         * @see net.sf.ehcache.constructs.blocking.BlockingCache
158         * @see #setCacheEntryFactory
159         */
160        public void setBlocking(boolean blocking) {
161                this.blocking = blocking;
162        }
163
164        /**
165         * Set an EhCache {@link net.sf.ehcache.constructs.blocking.CacheEntryFactory}
166         * to use for a self-populating cache. If such a factory is specified,
167         * the cache will be decorated with EhCache's
168         * {@link net.sf.ehcache.constructs.blocking.SelfPopulatingCache}.
169         * <p>The specified factory can be of type
170         * {@link net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory},
171         * which will lead to the use of an
172         * {@link net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache}.
173         * <p>Note: Any such self-populating cache is automatically a blocking cache.
174         * @see net.sf.ehcache.constructs.blocking.SelfPopulatingCache
175         * @see net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache
176         * @see net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory
177         */
178        public void setCacheEntryFactory(CacheEntryFactory cacheEntryFactory) {
179                this.cacheEntryFactory = cacheEntryFactory;
180        }
181
182        /**
183         * Set an EhCache {@link net.sf.ehcache.bootstrap.BootstrapCacheLoader}
184         * for this cache, if any.
185         */
186        public void setBootstrapCacheLoader(BootstrapCacheLoader bootstrapCacheLoader) {
187                this.bootstrapCacheLoader = bootstrapCacheLoader;
188        }
189
190        /**
191         * Specify EhCache {@link net.sf.ehcache.event.CacheEventListener cache event listeners}
192         * to registered with this cache.
193         */
194        public void setCacheEventListeners(Set<CacheEventListener> cacheEventListeners) {
195                this.cacheEventListeners = cacheEventListeners;
196        }
197
198        /**
199         * Set whether to enable EhCache statistics on this cache.
200         * <p>Note: As of EhCache 2.7, statistics are enabled by default, and cannot be turned off.
201         * This setter therefore has no effect in such a scenario.
202         * @see net.sf.ehcache.Ehcache#setStatisticsEnabled
203         */
204        public void setStatisticsEnabled(boolean statisticsEnabled) {
205                this.statisticsEnabled = statisticsEnabled;
206        }
207
208        /**
209         * Set whether to enable EhCache's sampled statistics on this cache.
210         * <p>Note: As of EhCache 2.7, statistics are enabled by default, and cannot be turned off.
211         * This setter therefore has no effect in such a scenario.
212         * @see net.sf.ehcache.Ehcache#setSampledStatisticsEnabled
213         */
214        public void setSampledStatisticsEnabled(boolean sampledStatisticsEnabled) {
215                this.sampledStatisticsEnabled = sampledStatisticsEnabled;
216        }
217
218        /**
219         * Set whether this cache should be marked as disabled.
220         * @see net.sf.ehcache.Cache#setDisabled
221         */
222        public void setDisabled(boolean disabled) {
223                this.disabled = disabled;
224        }
225
226        @Override
227        public void setBeanName(String name) {
228                this.beanName = name;
229        }
230
231
232        @Override
233        public void afterPropertiesSet() throws CacheException {
234                // If no cache name given, use bean name as cache name.
235                String cacheName = getName();
236                if (cacheName == null) {
237                        cacheName = this.beanName;
238                        setName(cacheName);
239                }
240
241                // If no CacheManager given, fetch the default.
242                if (this.cacheManager == null) {
243                        if (logger.isDebugEnabled()) {
244                                logger.debug("Using default EhCache CacheManager for cache region '" + cacheName + "'");
245                        }
246                        this.cacheManager = CacheManager.getInstance();
247                }
248
249                synchronized (this.cacheManager) {
250                        // Fetch cache region: If none with the given name exists, create one on the fly.
251                        Ehcache rawCache;
252                        boolean cacheExists = this.cacheManager.cacheExists(cacheName);
253
254                        if (cacheExists) {
255                                if (logger.isDebugEnabled()) {
256                                        logger.debug("Using existing EhCache cache region '" + cacheName + "'");
257                                }
258                                rawCache = this.cacheManager.getEhcache(cacheName);
259                        }
260                        else {
261                                if (logger.isDebugEnabled()) {
262                                        logger.debug("Creating new EhCache cache region '" + cacheName + "'");
263                                }
264                                rawCache = createCache();
265                                rawCache.setBootstrapCacheLoader(this.bootstrapCacheLoader);
266                        }
267
268                        if (this.cacheEventListeners != null) {
269                                for (CacheEventListener listener : this.cacheEventListeners) {
270                                        rawCache.getCacheEventNotificationService().registerListener(listener);
271                                }
272                        }
273
274                        // Needs to happen after listener registration but before setStatisticsEnabled
275                        if (!cacheExists) {
276                                this.cacheManager.addCache(rawCache);
277                        }
278
279                        // Only necessary on EhCache <2.7: As of 2.7, statistics are on by default.
280                        if (this.statisticsEnabled && setStatisticsEnabledMethod != null) {
281                                ReflectionUtils.invokeMethod(setStatisticsEnabledMethod, rawCache, true);
282                        }
283                        if (this.sampledStatisticsEnabled && setSampledStatisticsEnabledMethod != null) {
284                                ReflectionUtils.invokeMethod(setSampledStatisticsEnabledMethod, rawCache, true);
285                        }
286
287                        if (this.disabled) {
288                                rawCache.setDisabled(true);
289                        }
290
291                        Ehcache decoratedCache = decorateCache(rawCache);
292                        if (decoratedCache != rawCache) {
293                                this.cacheManager.replaceCacheWithDecoratedCache(rawCache, decoratedCache);
294                        }
295                        this.cache = decoratedCache;
296                }
297        }
298
299        /**
300         * Create a raw Cache object based on the configuration of this FactoryBean.
301         */
302        protected Cache createCache() {
303                return new Cache(this);
304        }
305
306        /**
307         * Decorate the given Cache, if necessary.
308         * @param cache the raw Cache object, based on the configuration of this FactoryBean
309         * @return the (potentially decorated) cache object to be registered with the CacheManager
310         */
311        protected Ehcache decorateCache(Ehcache cache) {
312                if (this.cacheEntryFactory != null) {
313                        if (this.cacheEntryFactory instanceof UpdatingCacheEntryFactory) {
314                                return new UpdatingSelfPopulatingCache(cache, (UpdatingCacheEntryFactory) this.cacheEntryFactory);
315                        }
316                        else {
317                                return new SelfPopulatingCache(cache, this.cacheEntryFactory);
318                        }
319                }
320                if (this.blocking) {
321                        return new BlockingCache(cache);
322                }
323                return cache;
324        }
325
326
327        @Override
328        public Ehcache getObject() {
329                return this.cache;
330        }
331
332        /**
333         * Predict the particular {@code Ehcache} implementation that will be returned from
334         * {@link #getObject()} based on logic in {@link #createCache()} and
335         * {@link #decorateCache(Ehcache)} as orchestrated by {@link #afterPropertiesSet()}.
336         */
337        @Override
338        public Class<? extends Ehcache> getObjectType() {
339                if (this.cache != null) {
340                        return this.cache.getClass();
341                }
342                if (this.cacheEntryFactory != null) {
343                        if (this.cacheEntryFactory instanceof UpdatingCacheEntryFactory) {
344                                return UpdatingSelfPopulatingCache.class;
345                        }
346                        else {
347                                return SelfPopulatingCache.class;
348                        }
349                }
350                if (this.blocking) {
351                        return BlockingCache.class;
352                }
353                return Cache.class;
354        }
355
356        @Override
357        public boolean isSingleton() {
358                return true;
359        }
360
361}