View Javadoc
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 }