001/*
002 * Copyright 2002-2018 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.util.Set;
020
021import net.sf.ehcache.Cache;
022import net.sf.ehcache.CacheException;
023import net.sf.ehcache.CacheManager;
024import net.sf.ehcache.Ehcache;
025import net.sf.ehcache.bootstrap.BootstrapCacheLoader;
026import net.sf.ehcache.config.CacheConfiguration;
027import net.sf.ehcache.constructs.blocking.BlockingCache;
028import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
029import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
030import net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory;
031import net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache;
032import net.sf.ehcache.event.CacheEventListener;
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035
036import org.springframework.beans.factory.BeanNameAware;
037import org.springframework.beans.factory.FactoryBean;
038import org.springframework.beans.factory.InitializingBean;
039import org.springframework.lang.Nullable;
040
041/**
042 * {@link FactoryBean} that creates a named EhCache {@link net.sf.ehcache.Cache} instance
043 * (or a decorator that implements the {@link net.sf.ehcache.Ehcache} interface),
044 * representing a cache region within an EhCache {@link net.sf.ehcache.CacheManager}.
045 *
046 * <p>If the specified named cache is not configured in the cache configuration descriptor,
047 * this FactoryBean will construct an instance of a Cache with the provided name and the
048 * specified cache properties and add it to the CacheManager for later retrieval. If some
049 * or all properties are not set at configuration time, this FactoryBean will use defaults.
050 *
051 * <p>Note: If the named Cache instance is found, the properties will be ignored and the
052 * Cache instance will be retrieved from the CacheManager.
053 *
054 * <p>Note: As of Spring 5.0, Spring's EhCache support requires EhCache 2.10 or higher.
055 *
056 * @author Juergen Hoeller
057 * @author Dmitriy Kopylenko
058 * @since 1.1.1
059 * @see #setCacheManager
060 * @see EhCacheManagerFactoryBean
061 * @see net.sf.ehcache.Cache
062 */
063public class EhCacheFactoryBean extends CacheConfiguration implements FactoryBean<Ehcache>, BeanNameAware, InitializingBean {
064
065        protected final Log logger = LogFactory.getLog(getClass());
066
067        @Nullable
068        private CacheManager cacheManager;
069
070        private boolean blocking = false;
071
072        @Nullable
073        private CacheEntryFactory cacheEntryFactory;
074
075        @Nullable
076        private BootstrapCacheLoader bootstrapCacheLoader;
077
078        @Nullable
079        private Set<CacheEventListener> cacheEventListeners;
080
081        private boolean disabled = false;
082
083        @Nullable
084        private String beanName;
085
086        @Nullable
087        private Ehcache cache;
088
089
090        public EhCacheFactoryBean() {
091                setMaxEntriesLocalHeap(10000);
092                setMaxEntriesLocalDisk(10000000);
093                setTimeToLiveSeconds(120);
094                setTimeToIdleSeconds(120);
095        }
096
097
098        /**
099         * Set a CacheManager from which to retrieve a named Cache instance.
100         * By default, {@code CacheManager.getInstance()} will be called.
101         * <p>Note that in particular for persistent caches, it is advisable to
102         * properly handle the shutdown of the CacheManager: Set up a separate
103         * EhCacheManagerFactoryBean and pass a reference to this bean property.
104         * <p>A separate EhCacheManagerFactoryBean is also necessary for loading
105         * EhCache configuration from a non-default config location.
106         * @see EhCacheManagerFactoryBean
107         * @see net.sf.ehcache.CacheManager#getInstance
108         */
109        public void setCacheManager(CacheManager cacheManager) {
110                this.cacheManager = cacheManager;
111        }
112
113        /**
114         * Set a name for which to retrieve or create a cache instance.
115         * Default is the bean name of this EhCacheFactoryBean.
116         */
117        public void setCacheName(String cacheName) {
118                setName(cacheName);
119        }
120
121        /**
122         * Set the time to live.
123         * @see #setTimeToLiveSeconds(long)
124         */
125        public void setTimeToLive(int timeToLive) {
126                setTimeToLiveSeconds(timeToLive);
127        }
128
129        /**
130         * Set the time to idle.
131         * @see #setTimeToIdleSeconds(long)
132         */
133        public void setTimeToIdle(int timeToIdle) {
134                setTimeToIdleSeconds(timeToIdle);
135        }
136
137        /**
138         * Set the disk spool buffer size (in MB).
139         * @see #setDiskSpoolBufferSizeMB(int)
140         */
141        public void setDiskSpoolBufferSize(int diskSpoolBufferSize) {
142                setDiskSpoolBufferSizeMB(diskSpoolBufferSize);
143        }
144
145        /**
146         * Set whether to use a blocking cache that lets read attempts block
147         * until the requested element is created.
148         * <p>If you intend to build a self-populating blocking cache,
149         * consider specifying a {@link #setCacheEntryFactory CacheEntryFactory}.
150         * @see net.sf.ehcache.constructs.blocking.BlockingCache
151         * @see #setCacheEntryFactory
152         */
153        public void setBlocking(boolean blocking) {
154                this.blocking = blocking;
155        }
156
157        /**
158         * Set an EhCache {@link net.sf.ehcache.constructs.blocking.CacheEntryFactory}
159         * to use for a self-populating cache. If such a factory is specified,
160         * the cache will be decorated with EhCache's
161         * {@link net.sf.ehcache.constructs.blocking.SelfPopulatingCache}.
162         * <p>The specified factory can be of type
163         * {@link net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory},
164         * which will lead to the use of an
165         * {@link net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache}.
166         * <p>Note: Any such self-populating cache is automatically a blocking cache.
167         * @see net.sf.ehcache.constructs.blocking.SelfPopulatingCache
168         * @see net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache
169         * @see net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory
170         */
171        public void setCacheEntryFactory(CacheEntryFactory cacheEntryFactory) {
172                this.cacheEntryFactory = cacheEntryFactory;
173        }
174
175        /**
176         * Set an EhCache {@link net.sf.ehcache.bootstrap.BootstrapCacheLoader}
177         * for this cache, if any.
178         */
179        public void setBootstrapCacheLoader(BootstrapCacheLoader bootstrapCacheLoader) {
180                this.bootstrapCacheLoader = bootstrapCacheLoader;
181        }
182
183        /**
184         * Specify EhCache {@link net.sf.ehcache.event.CacheEventListener cache event listeners}
185         * to registered with this cache.
186         */
187        public void setCacheEventListeners(Set<CacheEventListener> cacheEventListeners) {
188                this.cacheEventListeners = cacheEventListeners;
189        }
190
191        /**
192         * Set whether this cache should be marked as disabled.
193         * @see net.sf.ehcache.Cache#setDisabled
194         */
195        public void setDisabled(boolean disabled) {
196                this.disabled = disabled;
197        }
198
199        @Override
200        public void setBeanName(String name) {
201                this.beanName = name;
202        }
203
204
205        @Override
206        public void afterPropertiesSet() throws CacheException {
207                // If no cache name given, use bean name as cache name.
208                String cacheName = getName();
209                if (cacheName == null) {
210                        cacheName = this.beanName;
211                        if (cacheName != null) {
212                                setName(cacheName);
213                        }
214                }
215
216                // If no CacheManager given, fetch the default.
217                if (this.cacheManager == null) {
218                        if (logger.isDebugEnabled()) {
219                                logger.debug("Using default EhCache CacheManager for cache region '" + cacheName + "'");
220                        }
221                        this.cacheManager = CacheManager.getInstance();
222                }
223
224                synchronized (this.cacheManager) {
225                        // Fetch cache region: If none with the given name exists, create one on the fly.
226                        Ehcache rawCache;
227                        boolean cacheExists = this.cacheManager.cacheExists(cacheName);
228
229                        if (cacheExists) {
230                                if (logger.isDebugEnabled()) {
231                                        logger.debug("Using existing EhCache cache region '" + cacheName + "'");
232                                }
233                                rawCache = this.cacheManager.getEhcache(cacheName);
234                        }
235                        else {
236                                if (logger.isDebugEnabled()) {
237                                        logger.debug("Creating new EhCache cache region '" + cacheName + "'");
238                                }
239                                rawCache = createCache();
240                                rawCache.setBootstrapCacheLoader(this.bootstrapCacheLoader);
241                        }
242
243                        if (this.cacheEventListeners != null) {
244                                for (CacheEventListener listener : this.cacheEventListeners) {
245                                        rawCache.getCacheEventNotificationService().registerListener(listener);
246                                }
247                        }
248
249                        // Needs to happen after listener registration but before setStatisticsEnabled
250                        if (!cacheExists) {
251                                this.cacheManager.addCache(rawCache);
252                        }
253
254                        if (this.disabled) {
255                                rawCache.setDisabled(true);
256                        }
257
258                        Ehcache decoratedCache = decorateCache(rawCache);
259                        if (decoratedCache != rawCache) {
260                                this.cacheManager.replaceCacheWithDecoratedCache(rawCache, decoratedCache);
261                        }
262                        this.cache = decoratedCache;
263                }
264        }
265
266        /**
267         * Create a raw Cache object based on the configuration of this FactoryBean.
268         */
269        protected Cache createCache() {
270                return new Cache(this);
271        }
272
273        /**
274         * Decorate the given Cache, if necessary.
275         * @param cache the raw Cache object, based on the configuration of this FactoryBean
276         * @return the (potentially decorated) cache object to be registered with the CacheManager
277         */
278        protected Ehcache decorateCache(Ehcache cache) {
279                if (this.cacheEntryFactory != null) {
280                        if (this.cacheEntryFactory instanceof UpdatingCacheEntryFactory) {
281                                return new UpdatingSelfPopulatingCache(cache, (UpdatingCacheEntryFactory) this.cacheEntryFactory);
282                        }
283                        else {
284                                return new SelfPopulatingCache(cache, this.cacheEntryFactory);
285                        }
286                }
287                if (this.blocking) {
288                        return new BlockingCache(cache);
289                }
290                return cache;
291        }
292
293
294        @Override
295        @Nullable
296        public Ehcache getObject() {
297                return this.cache;
298        }
299
300        /**
301         * Predict the particular {@code Ehcache} implementation that will be returned from
302         * {@link #getObject()} based on logic in {@link #createCache()} and
303         * {@link #decorateCache(Ehcache)} as orchestrated by {@link #afterPropertiesSet()}.
304         */
305        @Override
306        public Class<? extends Ehcache> getObjectType() {
307                if (this.cache != null) {
308                        return this.cache.getClass();
309                }
310                if (this.cacheEntryFactory != null) {
311                        if (this.cacheEntryFactory instanceof UpdatingCacheEntryFactory) {
312                                return UpdatingSelfPopulatingCache.class;
313                        }
314                        else {
315                                return SelfPopulatingCache.class;
316                        }
317                }
318                if (this.blocking) {
319                        return BlockingCache.class;
                }
321                return Cache.class;
322        }
323
324        @Override
325        public boolean isSingleton() {
326                return true;
327        }
328
329}