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.session.mgt.eis;
20  
21  import org.apache.shiro.cache.Cache;
22  import org.apache.shiro.cache.CacheManager;
23  import org.apache.shiro.cache.CacheManagerAware;
24  import org.apache.shiro.session.Session;
25  import org.apache.shiro.session.UnknownSessionException;
26  import org.apache.shiro.session.mgt.ValidatingSession;
27  
28  import java.io.Serializable;
29  import java.util.Collection;
30  import java.util.Collections;
31  
32  /**
33   * An CachingSessionDAO is a SessionDAO that provides a transparent caching layer between the components that
34   * use it and the underlying EIS (Enterprise Information System) session backing store (for example, filesystem,
35   * database, enterprise grid/cloud, etc).
36   * <p/>
37   * This implementation caches all active sessions in a configured
38   * {@link #getActiveSessionsCache() activeSessionsCache}.  This property is {@code null} by default and if one is
39   * not explicitly set, a {@link #setCacheManager cacheManager} is expected to be configured which will in turn be used
40   * to acquire the {@code Cache} instance to use for the {@code activeSessionsCache}.
41   * <p/>
42   * All {@code SessionDAO} methods are implemented by this class to employ
43   * caching behavior and delegates the actual EIS operations to respective do* methods to be implemented by
44   * subclasses (doCreate, doRead, etc).
45   *
46   * @since 0.2
47   */
48  public abstract class CachingSessionDAO extends AbstractSessionDAO implements CacheManagerAware {
49  
50      /**
51       * The default active sessions cache name, equal to {@code shiro-activeSessionCache}.
52       */
53      public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache";
54  
55      /**
56       * The CacheManager to use to acquire the Session cache.
57       */
58      private CacheManager cacheManager;
59  
60      /**
61       * The Cache instance responsible for caching Sessions.
62       */
63      private Cache<Serializable, Session> activeSessions;
64  
65      /**
66       * The name of the session cache, defaults to {@link #ACTIVE_SESSION_CACHE_NAME}.
67       */
68      private String activeSessionsCacheName = ACTIVE_SESSION_CACHE_NAME;
69  
70      /**
71       * Default no-arg constructor.
72       */
73      public CachingSessionDAO() {
74      }
75  
76      /**
77       * Sets the cacheManager to use for acquiring the {@link #getActiveSessionsCache() activeSessionsCache} if
78       * one is not configured.
79       *
80       * @param cacheManager the manager to use for constructing the session cache.
81       */
82      public void setCacheManager(CacheManager cacheManager) {
83          this.cacheManager = cacheManager;
84      }
85  
86      /**
87       * Returns the CacheManager to use for acquiring the {@link #getActiveSessionsCache() activeSessionsCache} if
88       * one is not configured.  That is, the {@code CacheManager} will only be used if the
89       * {@link #getActiveSessionsCache() activeSessionsCache} property is {@code null}.
90       *
91       * @return the CacheManager used by the implementation that creates the activeSessions Cache.
92       */
93      public CacheManager getCacheManager() {
94          return cacheManager;
95      }
96  
97      /**
98       * Returns the name of the actives sessions cache to be returned by the {@code CacheManager}.  Unless
99       * overridden by {@link #setActiveSessionsCacheName(String)}, defaults to {@link #ACTIVE_SESSION_CACHE_NAME}.
100      *
101      * @return the name of the active sessions cache.
102      */
103     public String getActiveSessionsCacheName() {
104         return activeSessionsCacheName;
105     }
106 
107     /**
108      * Sets the name of the active sessions cache to be returned by the {@code CacheManager}.  Defaults to
109      * {@link #ACTIVE_SESSION_CACHE_NAME}.
110      *
111      * @param activeSessionsCacheName the name of the active sessions cache to be returned by the {@code CacheManager}.
112      */
113     public void setActiveSessionsCacheName(String activeSessionsCacheName) {
114         this.activeSessionsCacheName = activeSessionsCacheName;
115     }
116 
117     /**
118      * Returns the cache instance to use for storing active sessions.  If one is not available (it is {@code null}),
119      * it will be {@link CacheManager#getCache(String) acquired} from the {@link #setCacheManager configured}
120      * {@code CacheManager} using the {@link #getActiveSessionsCacheName() activeSessionsCacheName}.
121      *
122      * @return the cache instance to use for storing active sessions or {@code null} if the {@code Cache} instance
123      *         should be retrieved from the
124      */
125     public Cache<Serializable, Session> getActiveSessionsCache() {
126         return this.activeSessions;
127     }
128 
129     /**
130      * Sets the cache instance to use for storing active sessions.  If one is not set (it remains {@code null}),
131      * it will be {@link CacheManager#getCache(String) acquired} from the {@link #setCacheManager configured}
132      * {@code CacheManager} using the {@link #getActiveSessionsCacheName() activeSessionsCacheName}.
133      *
134      * @param cache the cache instance to use for storing active sessions or {@code null} if the cache is to be
135      *              acquired from the {@link #setCacheManager configured} {@code CacheManager}.
136      */
137     public void setActiveSessionsCache(Cache<Serializable, Session> cache) {
138         this.activeSessions = cache;
139     }
140 
141     /**
142      * Returns the active sessions cache, but if that cache instance is null, first lazily creates the cache instance
143      * via the {@link #createActiveSessionsCache()} method and then returns the instance.
144      * <p/>
145      * Note that this method will only return a non-null value code if the {@code CacheManager} has been set.  If
146      * not set, there will be no cache.
147      *
148      * @return the active sessions cache instance.
149      */
150     private Cache<Serializable, Session> getActiveSessionsCacheLazy() {
151         if (this.activeSessions == null) {
152             this.activeSessions = createActiveSessionsCache();
153         }
154         return activeSessions;
155     }
156 
157     /**
158      * Creates a cache instance used to store active sessions.  Creation is done by first
159      * {@link #getCacheManager() acquiring} the {@code CacheManager}.  If the cache manager is not null, the
160      * cache returned is that resulting from the following call:
161      * <pre>       String name = {@link #getActiveSessionsCacheName() getActiveSessionsCacheName()};
162      * cacheManager.getCache(name);</pre>
163      *
164      * @return a cache instance used to store active sessions, or {@code null} if the {@code CacheManager} has
165      *         not been set.
166      */
167     protected Cache<Serializable, Session> createActiveSessionsCache() {
168         Cache<Serializable, Session> cache = null;
169         CacheManager mgr = getCacheManager();
170         if (mgr != null) {
171             String name = getActiveSessionsCacheName();
172             cache = mgr.getCache(name);
173         }
174         return cache;
175     }
176 
177     /**
178      * Calls {@code super.create(session)}, then caches the session keyed by the returned {@code sessionId}, and then
179      * returns this {@code sessionId}.
180      *
181      * @param session Session object to create in the EIS and then cache.
182      */
183     public Serializable create(Session session) {
184         Serializable sessionId = super.create(session);
185         cache(session, sessionId);
186         return sessionId;
187     }
188 
189     /**
190      * Returns the cached session with the corresponding {@code sessionId} or {@code null} if there is
191      * no session cached under that id (or if there is no Cache).
192      *
193      * @param sessionId the id of the cached session to acquire.
194      * @return the cached session with the corresponding {@code sessionId}, or {@code null} if the session
195      *         does not exist or is not cached.
196      */
197     protected Session getCachedSession(Serializable sessionId) {
198         Session cached = null;
199         if (sessionId != null) {
200             Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
201             if (cache != null) {
202                 cached = getCachedSession(sessionId, cache);
203             }
204         }
205         return cached;
206     }
207 
208     /**
209      * Returns the Session with the specified id from the specified cache.  This method simply calls
210      * {@code cache.get(sessionId)} and can be overridden by subclasses for custom acquisition behavior.
211      *
212      * @param sessionId the id of the session to acquire.
213      * @param cache     the cache to acquire the session from
214      * @return the cached session, or {@code null} if the session wasn't in the cache.
215      */
216     protected Session getCachedSession(Serializable sessionId, Cache<Serializable, Session> cache) {
217         return cache.get(sessionId);
218     }
219 
220     /**
221      * Caches the specified session under the cache entry key of {@code sessionId}.
222      *
223      * @param session   the session to cache
224      * @param sessionId the session id, to be used as the cache entry key.
225      * @since 1.0
226      */
227     protected void cache(Session session, Serializable sessionId) {
228         if (session == null || sessionId == null) {
229             return;
230         }
231         Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
232         if (cache == null) {
233             return;
234         }
235         cache(session, sessionId, cache);
236     }
237 
238     /**
239      * Caches the specified session in the given cache under the key of {@code sessionId}.  This implementation
240      * simply calls {@code cache.put(sessionId,session)} and can be overridden for custom behavior.
241      *
242      * @param session   the session to cache
243      * @param sessionId the id of the session, expected to be the cache key.
244      * @param cache     the cache to store the session
245      */
246     protected void cache(Session session, Serializable sessionId, Cache<Serializable, Session> cache) {
247         cache.put(sessionId, session);
248     }
249 
250     /**
251      * Attempts to acquire the Session from the cache first using the session ID as the cache key.  If no session
252      * is found, {@code super.readSession(sessionId)} is called to perform the actual retrieval.
253      *
254      * @param sessionId the id of the session to retrieve from the EIS.
255      * @return the session identified by {@code sessionId} in the EIS.
256      * @throws UnknownSessionException if the id specified does not correspond to any session in the cache or EIS.
257      */
258     public Session readSession(Serializable sessionId) throws UnknownSessionException {
259         Session s = getCachedSession(sessionId);
260         if (s == null) {
261             s = super.readSession(sessionId);
262         }
263         return s;
264     }
265 
266     /**
267      * Updates the state of the given session to the EIS by first delegating to
268      * {@link #doUpdate(org.apache.shiro.session.Session)}.  If the session is a {@link ValidatingSession}, it will
269      * be added to the cache only if it is {@link ValidatingSession#isValid()} and if invalid, will be removed from the
270      * cache.  If it is not a {@code ValidatingSession} instance, it will be added to the cache in any event.
271      *
272      * @param session the session object to update in the EIS.
273      * @throws UnknownSessionException if no existing EIS session record exists with the
274      *                                 identifier of {@link Session#getId() session.getId()}
275      */
276     public void update(Session session) throws UnknownSessionException {
277         doUpdate(session);
278         if (session instanceof ValidatingSession) {
279             if (((ValidatingSession) session).isValid()) {
280                 cache(session, session.getId());
281             } else {
282                 uncache(session);
283             }
284         } else {
285             cache(session, session.getId());
286         }
287     }
288 
289     /**
290      * Subclass implementation hook to actually persist the {@code Session}'s state to the underlying EIS.
291      *
292      * @param session the session object whose state will be propagated to the EIS.
293      */
294     protected abstract void doUpdate(Session session);
295 
296     /**
297      * Removes the specified session from any cache and then permanently deletes the session from the EIS by
298      * delegating to {@link #doDelete}.
299      *
300      * @param session the session to remove from caches and permanently delete from the EIS.
301      */
302     public void delete(Session session) {
303         uncache(session);
304         doDelete(session);
305     }
306 
307     /**
308      * Subclass implementation hook to permanently delete the given Session from the underlying EIS.
309      *
310      * @param session the session instance to permanently delete from the EIS.
311      */
312     protected abstract void doDelete(Session session);
313 
314     /**
315      * Removes the specified Session from the cache.
316      *
317      * @param session the session to remove from the cache.
318      */
319     protected void uncache(Session session) {
320         if (session == null) {
321             return;
322         }
323         Serializable id = session.getId();
324         if (id == null) {
325             return;
326         }
327         Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
328         if (cache != null) {
329             cache.remove(id);
330         }
331     }
332 
333     /**
334      * Returns all active sessions in the system.
335      * <p/>
336      * <p>This implementation merely returns the sessions found in the activeSessions cache.  Subclass implementations
337      * may wish to override this method to retrieve them in a different way, perhaps by an RDBMS query or by other
338      * means.
339      *
340      * @return the sessions found in the activeSessions cache.
341      */
342     public Collection<Session> getActiveSessions() {
343         Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
344         if (cache != null) {
345             return cache.values();
346         } else {
347             return Collections.emptySet();
348         }
349     }
350 }