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 }