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 */
019package org.apache.shiro.cache.ehcache;
020
021import org.apache.shiro.cache.Cache;
022import org.apache.shiro.cache.CacheException;
023import org.apache.shiro.cache.CacheManager;
024import org.apache.shiro.config.ConfigurationException;
025import org.apache.shiro.io.ResourceUtils;
026import org.apache.shiro.util.Destroyable;
027import org.apache.shiro.util.Initializable;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031import java.io.IOException;
032import 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 */
050public 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 (Throwable t) {
241                if (log.isWarnEnabled()) {
242                    log.warn("Unable to cleanly shutdown implicitly created CacheManager instance.  " +
243                            "Ignoring (shutting down)...", t);
244                }
245            } finally {
246                this.manager = null;
247                this.cacheManagerImplicitlyCreated = false;
248            }
249        }
250    }
251}