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.config.ConfigurationException;
25  import org.apache.shiro.io.ResourceUtils;
26  import org.apache.shiro.util.Destroyable;
27  import org.apache.shiro.util.Initializable;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  import java.io.IOException;
32  import java.io.InputStream;
33  
34  /**
35   * Shiro {@code CacheManager} implementation utilizing the Ehcache framework for all cache functionality.
36   * <p/>
37   * This class can {@link #setCacheManager(net.sf.ehcache.CacheManager) accept} a manually configured
38   * {@link net.sf.ehcache.CacheManager net.sf.ehcache.CacheManager} instance,
39   * or an {@code ehcache.xml} path location can be specified instead and one will be constructed. If neither are
40   * specified, Shiro's failsafe <code><a href="./ehcache.xml">ehcache.xml</a>} file will be used by default.
41   * <p/>
42   * This implementation requires EhCache 1.2 and above. Make sure EhCache 1.1 or earlier
43   * is not in the classpath or it will not work.
44   * <p/>
45   * Please see the <a href="http://ehcache.sf.net" target="_top">Ehcache website</a> for their documentation.
46   *
47   * @see <a href="http://ehcache.sf.net" target="_top">The Ehcache website</a>
48   * @since 0.2
49   */
50  public class EhCacheManager implements CacheManager, Initializable, Destroyable {
51  
52      /**
53       * This class's private log instance.
54       */
55      private static final Logger log = LoggerFactory.getLogger(EhCacheManager.class);
56  
57      /**
58       * The EhCache cache manager used by this implementation to create caches.
59       */
60      protected net.sf.ehcache.CacheManager manager;
61  
62      /**
63       * Indicates if the CacheManager instance was implicitly/automatically created by this instance, indicating that
64       * it should be automatically cleaned up as well on shutdown.
65       */
66      private boolean cacheManagerImplicitlyCreated = false;
67  
68      /**
69       * Classpath file location of the ehcache CacheManager config file.
70       */
71      private String cacheManagerConfigFile = "classpath:org/apache/shiro/cache/ehcache/ehcache.xml";
72  
73      /**
74       * Default no argument constructor
75       */
76      public EhCacheManager() {
77      }
78  
79      /**
80       * Returns the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
81       *
82       * @return the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
83       */
84      public net.sf.ehcache.CacheManager getCacheManager() {
85          return manager;
86      }
87  
88      /**
89       * Sets the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
90       *
91       * @param manager the wrapped Ehcache {@link net.sf.ehcache.CacheManager CacheManager} instance.
92       */
93      public void setCacheManager(net.sf.ehcache.CacheManager manager) {
94          this.manager = manager;
95      }
96  
97      /**
98       * Returns the resource location of the config file used to initialize a new
99       * 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 }