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 }