/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.shiro.cache.ehcache; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.io.ResourceUtils; import org.apache.shiro.util.Destroyable; import org.apache.shiro.util.Initializable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; /** * Shiro {@code CacheManager} implementation utilizing the Ehcache framework for all cache functionality. * <p/> * This class can {@link #setCacheManager(net.sf.ehcache.CacheManager) accept} a manually configured * {@link net.sf.ehcache.CacheManager net.sf.ehcache.CacheManager} instance, * or an {@code ehcache.xml} path location can be specified instead and one will be constructed. If neither are * specified, Shiro's failsafe <code><a href="./ehcache.xml">ehcache.xml</a></code> file will be used by default. * <p/> * This implementation requires EhCache 1.2 and above. Make sure EhCache 1.1 or earlier * is not in the classpath or it will not work. * <p/> * Please see the <a href="http://ehcache.sf.net" target="_top">Ehcache website</a> for their documentation. * * @see <a href="http://ehcache.sf.net" target="_top">The Ehcache website</a> * @since 0.2 */ public class EhCacheManager implements CacheManager, Initializable, Destroyable { /** * This class's private log instance. */ private static final Logger log = LoggerFactory.getLogger(EhCacheManager.class); /** * The EhCache cache manager used by this implementation to create caches. */ protected net.sf.ehcache.CacheManager manager; /** * Indicates if the CacheManager instance was implicitly/automatically created by this instance, indicating that * it should be automatically cleaned up as well on shutdown. */ private boolean cacheManagerImplicitlyCreated = false; /** * Classpath file location of the ehcache CacheManager config file. */ private String cacheManagerConfigFile = "classpath:org/apache/shiro/cache/ehcache/ehcache.xml"; /** * Default no argument constructor */ public EhCacheManager() { } /** * Returns the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance. * * @return the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance. */ public net.sf.ehcache.CacheManager getCacheManager() { return manager; } /** * Sets the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance. * * @param manager the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance. */ public void setCacheManager(net.sf.ehcache.CacheManager manager) { this.manager = manager; } /** * Returns the resource location of the config file used to initialize a new * EhCache CacheManager instance. The string can be any resource path supported by the * {@link org.apache.shiro.io.ResourceUtils#getInputStreamForPath(String)} call. * <p/> * This property is ignored if the CacheManager instance is injected directly - that is, it is only used to * lazily create a CacheManager if one is not already provided. * * @return the resource location of the config file used to initialize the wrapped * EhCache CacheManager instance. */ public String getCacheManagerConfigFile() { return this.cacheManagerConfigFile; } /** * Sets the resource location of the config file used to initialize the wrapped * EhCache CacheManager instance. The string can be any resource path supported by the * {@link org.apache.shiro.io.ResourceUtils#getInputStreamForPath(String)} call. * <p/> * This property is ignored if the CacheManager instance is injected directly - that is, it is only used to * lazily create a CacheManager if one is not already provided. * * @param classpathLocation resource location of the config file used to create the wrapped * EhCache CacheManager instance. */ public void setCacheManagerConfigFile(String classpathLocation) { this.cacheManagerConfigFile = classpathLocation; } /** * Acquires the InputStream for the ehcache configuration file using * {@link ResourceUtils#getInputStreamForPath(String) ResourceUtils.getInputStreamForPath} with the * path returned from {@link #getCacheManagerConfigFile() getCacheManagerConfigFile()}. * * @return the InputStream for the ehcache configuration file. */ protected InputStream getCacheManagerConfigFileInputStream() { String configFile = getCacheManagerConfigFile(); try { return ResourceUtils.getInputStreamForPath(configFile); } catch (IOException e) { throw new IllegalStateException("Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e); } } /** * Loads an existing EhCache from the cache manager, or starts a new cache if one is not found. * * @param name the name of the cache to load/create. */ public final <K, V> Cache<K, V> getCache(String name) throws CacheException { if (log.isTraceEnabled()) { log.trace("Acquiring EhCache instance named [" + name + "]"); } try { net.sf.ehcache.Ehcache cache = ensureCacheManager().getEhcache(name); if (cache == null) { if (log.isInfoEnabled()) { log.info("Cache with name '{}' does not yet exist. Creating now.", name); } this.manager.addCache(name); cache = manager.getCache(name); if (log.isInfoEnabled()) { log.info("Added EhCache named [" + name + "]"); } } else { if (log.isInfoEnabled()) { log.info("Using existing EHCache named [" + cache.getName() + "]"); } } return new EhCache<K, V>(cache); } catch (net.sf.ehcache.CacheException e) { throw new CacheException(e); } } /** * Initializes this instance. * <p/> * If a {@link #setCacheManager CacheManager} has been * explicitly set (e.g. via Dependency Injection or programmatically) prior to calling this * method, this method does nothing. * <p/> * However, if no {@code CacheManager} has been set, the default Ehcache singleton will be initialized, where * Ehcache will look for an {@code ehcache.xml} file at the root of the classpath. If one is not found, * Ehcache will use its own failsafe configuration file. * <p/> * Because Shiro cannot use the failsafe defaults (fail-safe expunges cached objects after 2 minutes, * something not desirable for Shiro sessions), this class manages an internal default configuration for * this case. * * @throws org.apache.shiro.cache.CacheException * if there are any CacheExceptions thrown by EhCache. * @see net.sf.ehcache.CacheManager#create */ public final void init() throws CacheException { ensureCacheManager(); } private net.sf.ehcache.CacheManager ensureCacheManager() { try { if (this.manager == null) { if (log.isDebugEnabled()) { log.debug("cacheManager property not set. Constructing CacheManager instance... "); } //using the CacheManager constructor, the resulting instance is _not_ a VM singleton //(as would be the case by calling CacheManager.getInstance(). We do not use the getInstance here //because we need to know if we need to destroy the CacheManager instance - using the static call, //we don't know which component is responsible for shutting it down. By using a single EhCacheManager, //it will always know to shut down the instance if it was responsible for creating it. this.manager = new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream()); if (log.isTraceEnabled()) { log.trace("instantiated Ehcache CacheManager instance."); } cacheManagerImplicitlyCreated = true; if (log.isDebugEnabled()) { log.debug("implicit cacheManager created successfully."); } } return this.manager; } catch (Exception e) { throw new CacheException(e); } } /** * Shuts-down the wrapped Ehcache CacheManager <b>only if implicitly created</b>. * <p/> * If another component injected * a non-null CacheManager into this instance before calling {@link #init() init}, this instance expects that same * component to also destroy the CacheManager instance, and it will not attempt to do so. */ public void destroy() { if (cacheManagerImplicitlyCreated) { try { net.sf.ehcache.CacheManager cacheMgr = getCacheManager(); cacheMgr.shutdown(); } catch (Throwable t) { if (log.isWarnEnabled()) { log.warn("Unable to cleanly shutdown implicitly created CacheManager instance. " + "Ignoring (shutting down)...", t); } } finally { this.manager = null; this.cacheManagerImplicitlyCreated = false; } } } }