1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.apache.shiro.cache.ehcache; 20 21 import org.apache.shiro.cache.Cache; 22 import org.apache.shiro.cache.CacheException; 23 import org.apache.shiro.cache.CacheManager; 24 import org.apache.shiro.io.ResourceUtils; 25 import org.apache.shiro.util.Destroyable; 26 import org.apache.shiro.util.Initializable; 27 import org.slf4j.Logger; 28 import org.slf4j.LoggerFactory; 29 30 import java.io.IOException; 31 import java.io.InputStream; 32 33 /** 34 * Shiro {@code CacheManager} implementation utilizing the Ehcache framework for all cache functionality. 35 * <p/> 36 * This class can {@link #setCacheManager(net.sf.ehcache.CacheManager) accept} a manually configured 37 * {@link net.sf.ehcache.CacheManager net.sf.ehcache.CacheManager} instance, 38 * or an {@code ehcache.xml} path location can be specified instead and one will be constructed. If neither are 39 * specified, Shiro's failsafe <code><a href="./ehcache.xml">ehcache.xml</a></code> file will be used by default. 40 * <p/> 41 * This implementation requires EhCache 1.2 and above. Make sure EhCache 1.1 or earlier 42 * is not in the classpath or it will not work. 43 * <p/> 44 * Please see the <a href="http://ehcache.sf.net" target="_top">Ehcache website</a> for their documentation. 45 * 46 * @see <a href="http://ehcache.sf.net" target="_top">The Ehcache website</a> 47 * @since 0.2 48 */ 49 public class EhCacheManager implements CacheManager, Initializable, Destroyable { 50 51 /** 52 * This class's private log instance. 53 */ 54 private static final Logger log = LoggerFactory.getLogger(EhCacheManager.class); 55 56 /** 57 * The EhCache cache manager used by this implementation to create caches. 58 */ 59 protected net.sf.ehcache.CacheManager manager; 60 61 /** 62 * Indicates if the CacheManager instance was implicitly/automatically created by this instance, indicating that 63 * it should be automatically cleaned up as well on shutdown. 64 */ 65 private boolean cacheManagerImplicitlyCreated = false; 66 67 /** 68 * Classpath file location of the ehcache CacheManager config file. 69 */ 70 private String cacheManagerConfigFile = "classpath:org/apache/shiro/cache/ehcache/ehcache.xml"; 71 72 /** 73 * Default no argument constructor 74 */ 75 public EhCacheManager() { 76 } 77 78 /** 79 * Returns the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance. 80 * 81 * @return the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance. 82 */ 83 public net.sf.ehcache.CacheManager getCacheManager() { 84 return manager; 85 } 86 87 /** 88 * Sets the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance. 89 * 90 * @param manager the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance. 91 */ 92 public void setCacheManager(net.sf.ehcache.CacheManager manager) { 93 this.manager = manager; 94 } 95 96 /** 97 * Returns the resource location of the config file used to initialize a new 98 * EhCache CacheManager instance. The string can be any resource path supported by the 99 * {@link org.apache.shiro.io.ResourceUtils#getInputStreamForPath(String)} call. 100 * <p/> 101 * This property is ignored if the CacheManager instance is injected directly - that is, it is only used to 102 * lazily create a CacheManager if one is not already provided. 103 * 104 * @return the resource location of the config file used to initialize the wrapped 105 * EhCache CacheManager instance. 106 */ 107 public String getCacheManagerConfigFile() { 108 return this.cacheManagerConfigFile; 109 } 110 111 /** 112 * Sets the resource location of the config file used to initialize the wrapped 113 * EhCache CacheManager instance. The string can be any resource path supported by the 114 * {@link org.apache.shiro.io.ResourceUtils#getInputStreamForPath(String)} call. 115 * <p/> 116 * This property is ignored if the CacheManager instance is injected directly - that is, it is only used to 117 * lazily create a CacheManager if one is not already provided. 118 * 119 * @param classpathLocation resource location of the config file used to create the wrapped 120 * EhCache CacheManager instance. 121 */ 122 public void setCacheManagerConfigFile(String classpathLocation) { 123 this.cacheManagerConfigFile = classpathLocation; 124 } 125 126 /** 127 * Acquires the InputStream for the ehcache configuration file using 128 * {@link ResourceUtils#getInputStreamForPath(String) ResourceUtils.getInputStreamForPath} with the 129 * path returned from {@link #getCacheManagerConfigFile() getCacheManagerConfigFile()}. 130 * 131 * @return the InputStream for the ehcache configuration file. 132 */ 133 protected InputStream getCacheManagerConfigFileInputStream() { 134 String configFile = getCacheManagerConfigFile(); 135 try { 136 return ResourceUtils.getInputStreamForPath(configFile); 137 } catch (IOException e) { 138 throw new IllegalStateException("Unable to obtain input stream for cacheManagerConfigFile [" + 139 configFile + "]", e); 140 } 141 } 142 143 /** 144 * Loads an existing EhCache from the cache manager, or starts a new cache if one is not found. 145 * 146 * @param name the name of the cache to load/create. 147 */ 148 public final <K, V> Cache<K, V> getCache(String name) throws CacheException { 149 150 if (log.isTraceEnabled()) { 151 log.trace("Acquiring EhCache instance named [" + name + "]"); 152 } 153 154 try { 155 net.sf.ehcache.Ehcache cache = ensureCacheManager().getEhcache(name); 156 if (cache == null) { 157 if (log.isInfoEnabled()) { 158 log.info("Cache with name '{}' does not yet exist. Creating now.", name); 159 } 160 this.manager.addCache(name); 161 162 cache = manager.getCache(name); 163 164 if (log.isInfoEnabled()) { 165 log.info("Added EhCache named [" + name + "]"); 166 } 167 } else { 168 if (log.isInfoEnabled()) { 169 log.info("Using existing EHCache named [" + cache.getName() + "]"); 170 } 171 } 172 return new EhCache<K, V>(cache); 173 } catch (net.sf.ehcache.CacheException e) { 174 throw new CacheException(e); 175 } 176 } 177 178 /** 179 * Initializes this instance. 180 * <p/> 181 * If a {@link #setCacheManager CacheManager} has been 182 * explicitly set (e.g. via Dependency Injection or programmatically) prior to calling this 183 * method, this method does nothing. 184 * <p/> 185 * However, if no {@code CacheManager} has been set, the default Ehcache singleton will be initialized, where 186 * Ehcache will look for an {@code ehcache.xml} file at the root of the classpath. If one is not found, 187 * Ehcache will use its own failsafe configuration file. 188 * <p/> 189 * Because Shiro cannot use the failsafe defaults (fail-safe expunges cached objects after 2 minutes, 190 * something not desirable for Shiro sessions), this class manages an internal default configuration for 191 * this case. 192 * 193 * @throws org.apache.shiro.cache.CacheException 194 * if there are any CacheExceptions thrown by EhCache. 195 * @see net.sf.ehcache.CacheManager#create 196 */ 197 public final void init() throws CacheException { 198 ensureCacheManager(); 199 } 200 201 private net.sf.ehcache.CacheManager ensureCacheManager() { 202 try { 203 if (this.manager == null) { 204 if (log.isDebugEnabled()) { 205 log.debug("cacheManager property not set. Constructing CacheManager instance... "); 206 } 207 //using the CacheManager constructor, the resulting instance is _not_ a VM singleton 208 //(as would be the case by calling CacheManager.getInstance(). We do not use the getInstance here 209 //because we need to know if we need to destroy the CacheManager instance - using the static call, 210 //we don't know which component is responsible for shutting it down. By using a single EhCacheManager, 211 //it will always know to shut down the instance if it was responsible for creating it. 212 this.manager = new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream()); 213 if (log.isTraceEnabled()) { 214 log.trace("instantiated Ehcache CacheManager instance."); 215 } 216 cacheManagerImplicitlyCreated = true; 217 if (log.isDebugEnabled()) { 218 log.debug("implicit cacheManager created successfully."); 219 } 220 } 221 return this.manager; 222 } catch (Exception e) { 223 throw new CacheException(e); 224 } 225 } 226 227 /** 228 * Shuts-down the wrapped Ehcache CacheManager <b>only if implicitly created</b>. 229 * <p/> 230 * If another component injected 231 * a non-null CacheManager into this instance before calling {@link #init() init}, this instance expects that same 232 * component to also destroy the CacheManager instance, and it will not attempt to do so. 233 */ 234 public void destroy() { 235 if (cacheManagerImplicitlyCreated) { 236 try { 237 net.sf.ehcache.CacheManager cacheMgr = getCacheManager(); 238 cacheMgr.shutdown(); 239 } catch (Throwable t) { 240 if (log.isWarnEnabled()) { 241 log.warn("Unable to cleanly shutdown implicitly created CacheManager instance. " + 242 "Ignoring (shutting down)...", t); 243 } 244 } finally { 245 this.manager = null; 246 this.cacheManagerImplicitlyCreated = false; 247 } 248 } 249 } 250 }