001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.shiro.cache.ehcache;
020    
021    import org.apache.shiro.cache.Cache;
022    import org.apache.shiro.cache.CacheException;
023    import org.apache.shiro.cache.CacheManager;
024    import org.apache.shiro.config.ConfigurationException;
025    import org.apache.shiro.io.ResourceUtils;
026    import org.apache.shiro.util.Destroyable;
027    import org.apache.shiro.util.Initializable;
028    import org.slf4j.Logger;
029    import org.slf4j.LoggerFactory;
030    
031    import java.io.IOException;
032    import java.io.InputStream;
033    
034    /**
035     * Shiro {@code CacheManager} implementation utilizing the Ehcache framework for all cache functionality.
036     * <p/>
037     * This class can {@link #setCacheManager(net.sf.ehcache.CacheManager) accept} a manually configured
038     * {@link net.sf.ehcache.CacheManager net.sf.ehcache.CacheManager} instance,
039     * or an {@code ehcache.xml} path location can be specified instead and one will be constructed. If neither are
040     * specified, Shiro's failsafe <code><a href="./ehcache.xml">ehcache.xml</a>} file will be used by default.
041     * <p/>
042     * This implementation requires EhCache 1.2 and above. Make sure EhCache 1.1 or earlier
043     * is not in the classpath or it will not work.
044     * <p/>
045     * Please see the <a href="http://ehcache.sf.net" target="_top">Ehcache website</a> for their documentation.
046     *
047     * @see <a href="http://ehcache.sf.net" target="_top">The Ehcache website</a>
048     * @since 0.2
049     */
050    public class EhCacheManager implements CacheManager, Initializable, Destroyable {
051    
052        /**
053         * This class's private log instance.
054         */
055        private static final Logger log = LoggerFactory.getLogger(EhCacheManager.class);
056    
057        /**
058         * The EhCache cache manager used by this implementation to create caches.
059         */
060        protected net.sf.ehcache.CacheManager manager;
061    
062        /**
063         * Indicates if the CacheManager instance was implicitly/automatically created by this instance, indicating that
064         * it should be automatically cleaned up as well on shutdown.
065         */
066        private boolean cacheManagerImplicitlyCreated = false;
067    
068        /**
069         * Classpath file location of the ehcache CacheManager config file.
070         */
071        private String cacheManagerConfigFile = "classpath:org/apache/shiro/cache/ehcache/ehcache.xml";
072    
073        /**
074         * Default no argument constructor
075         */
076        public EhCacheManager() {
077        }
078    
079        /**
080         * Returns the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
081         *
082         * @return the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
083         */
084        public net.sf.ehcache.CacheManager getCacheManager() {
085            return manager;
086        }
087    
088        /**
089         * Sets the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
090         *
091         * @param manager the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
092         */
093        public void setCacheManager(net.sf.ehcache.CacheManager manager) {
094            this.manager = manager;
095        }
096    
097        /**
098         * Returns the resource location of the config file used to initialize a new
099         * EhCache CacheManager instance.  The string can be any resource path supported by the
100         * {@link org.apache.shiro.io.ResourceUtils#getInputStreamForPath(String)} call.
101         * <p/>
102         * This property is ignored if the CacheManager instance is injected directly - that is, it is only used to
103         * lazily create a CacheManager if one is not already provided.
104         *
105         * @return the resource location of the config file used to initialize the wrapped
106         *         EhCache CacheManager instance.
107         */
108        public String getCacheManagerConfigFile() {
109            return this.cacheManagerConfigFile;
110        }
111    
112        /**
113         * Sets the resource location of the config file used to initialize the wrapped
114         * EhCache CacheManager instance.  The string can be any resource path supported by the
115         * {@link org.apache.shiro.io.ResourceUtils#getInputStreamForPath(String)} call.
116         * <p/>
117         * This property is ignored if the CacheManager instance is injected directly - that is, it is only used to
118         * lazily create a CacheManager if one is not already provided.
119         *
120         * @param classpathLocation resource location of the config file used to create the wrapped
121         *                          EhCache CacheManager instance.
122         */
123        public void setCacheManagerConfigFile(String classpathLocation) {
124            this.cacheManagerConfigFile = classpathLocation;
125        }
126    
127        /**
128         * Acquires the InputStream for the ehcache configuration file using
129         * {@link ResourceUtils#getInputStreamForPath(String) ResourceUtils.getInputStreamForPath} with the
130         * path returned from {@link #getCacheManagerConfigFile() getCacheManagerConfigFile()}.
131         *
132         * @return the InputStream for the ehcache configuration file.
133         */
134        protected InputStream getCacheManagerConfigFileInputStream() {
135            String configFile = getCacheManagerConfigFile();
136            try {
137                return ResourceUtils.getInputStreamForPath(configFile);
138            } catch (IOException e) {
139                throw new ConfigurationException("Unable to obtain input stream for cacheManagerConfigFile [" +
140                        configFile + "]", e);
141            }
142        }
143    
144        /**
145         * Loads an existing EhCache from the cache manager, or starts a new cache if one is not found.
146         *
147         * @param name the name of the cache to load/create.
148         */
149        public final <K, V> Cache<K, V> getCache(String name) throws CacheException {
150    
151            if (log.isTraceEnabled()) {
152                log.trace("Acquiring EhCache instance named [" + name + "]");
153            }
154    
155            try {
156                net.sf.ehcache.Ehcache cache = ensureCacheManager().getEhcache(name);
157                if (cache == null) {
158                    if (log.isInfoEnabled()) {
159                        log.info("Cache with name '{}' does not yet exist.  Creating now.", name);
160                    }
161                    this.manager.addCache(name);
162    
163                    cache = manager.getCache(name);
164    
165                    if (log.isInfoEnabled()) {
166                        log.info("Added EhCache named [" + name + "]");
167                    }
168                } else {
169                    if (log.isInfoEnabled()) {
170                        log.info("Using existing EHCache named [" + cache.getName() + "]");
171                    }
172                }
173                return new EhCache<K, V>(cache);
174            } catch (net.sf.ehcache.CacheException e) {
175                throw new CacheException(e);
176            }
177        }
178    
179        /**
180         * Initializes this instance.
181         * <p/>
182         * If a {@link #setCacheManager CacheManager} has been
183         * explicitly set (e.g. via Dependency Injection or programatically) prior to calling this
184         * method, this method does nothing.
185         * <p/>
186         * However, if no {@code CacheManager} has been set, the default Ehcache singleton will be initialized, where
187         * Ehcache will look for an {@code ehcache.xml} file at the root of the classpath.  If one is not found,
188         * Ehcache will use its own failsafe configuration file.
189         * <p/>
190         * Because Shiro cannot use the failsafe defaults (fail-safe expunges cached objects after 2 minutes,
191         * something not desirable for Shiro sessions), this class manages an internal default configuration for
192         * this case.
193         *
194         * @throws org.apache.shiro.cache.CacheException
195         *          if there are any CacheExceptions thrown by EhCache.
196         * @see net.sf.ehcache.CacheManager#create
197         */
198        public final void init() throws CacheException {
199            ensureCacheManager();
200        }
201    
202        private net.sf.ehcache.CacheManager ensureCacheManager() {
203            try {
204                if (this.manager == null) {
205                    if (log.isDebugEnabled()) {
206                        log.debug("cacheManager property not set.  Constructing CacheManager instance... ");
207                    }
208                    //using the CacheManager constructor, the resulting instance is _not_ a VM singleton
209                    //(as would be the case by calling CacheManager.getInstance().  We do not use the getInstance here
210                    //because we need to know if we need to destroy the CacheManager instance - using the static call,
211                    //we don't know which component is responsible for shutting it down.  By using a single EhCacheManager,
212                    //it will always know to shut down the instance if it was responsible for creating it.
213                    this.manager = new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream());
214                    if (log.isTraceEnabled()) {
215                        log.trace("instantiated Ehcache CacheManager instance.");
216                    }
217                    cacheManagerImplicitlyCreated = true;
218                    if (log.isDebugEnabled()) {
219                        log.debug("implicit cacheManager created successfully.");
220                    }
221                }
222                return this.manager;
223            } catch (Exception e) {
224                throw new CacheException(e);
225            }
226        }
227    
228        /**
229         * Shuts-down the wrapped Ehcache CacheManager <b>only if implicitly created</b>.
230         * <p/>
231         * If another component injected
232         * a non-null CacheManager into this instace before calling {@link #init() init}, this instance expects that same
233         * component to also destroy the CacheManager instance, and it will not attempt to do so.
234         */
235        public void destroy() {
236            if (cacheManagerImplicitlyCreated) {
237                try {
238                    net.sf.ehcache.CacheManager cacheMgr = getCacheManager();
239                    cacheMgr.shutdown();
240                } catch (Exception e) {
241                    if (log.isWarnEnabled()) {
242                        log.warn("Unable to cleanly shutdown implicitly created CacheManager instance.  " +
243                                "Ignoring (shutting down)...");
244                    }
245                }
246                cacheManagerImplicitlyCreated = false;
247            }
248        }
249    }