001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.session.mgt.eis;
020
021 import org.apache.shiro.cache.Cache;
022 import org.apache.shiro.cache.CacheManager;
023 import org.apache.shiro.cache.CacheManagerAware;
024 import org.apache.shiro.session.Session;
025 import org.apache.shiro.session.UnknownSessionException;
026 import org.apache.shiro.session.mgt.ValidatingSession;
027
028 import java.io.Serializable;
029 import java.util.Collection;
030 import java.util.Collections;
031
032 /**
033 * An CachingSessionDAO is a SessionDAO that provides a transparent caching layer between the components that
034 * use it and the underlying EIS (Enterprise Information System) session backing store (for example, filesystem,
035 * database, enterprise grid/cloud, etc).
036 * <p/>
037 * This implementation caches all active sessions in a configured
038 * {@link #getActiveSessionsCache() activeSessionsCache}. This property is {@code null} by default and if one is
039 * not explicitly set, a {@link #setCacheManager cacheManager} is expected to be configured which will in turn be used
040 * to acquire the {@code Cache} instance to use for the {@code activeSessionsCache}.
041 * <p/>
042 * All {@code SessionDAO} methods are implemented by this class to employ
043 * caching behavior and delegates the actual EIS operations to respective do* methods to be implemented by
044 * subclasses (doCreate, doRead, etc).
045 *
046 * @author Les Hazlewood
047 * @since 0.2
048 */
049 public abstract class CachingSessionDAO extends AbstractSessionDAO implements CacheManagerAware {
050
051 /**
052 * The default active sessions cache name, equal to {@code shiro-activeSessionCache}.
053 */
054 public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache";
055
056 /**
057 * The CacheManager to use to acquire the Session cache.
058 */
059 private CacheManager cacheManager;
060
061 /**
062 * The Cache instance responsible for caching Sessions.
063 */
064 private Cache<Serializable, Session> activeSessions;
065
066 /**
067 * The name of the session cache, defaults to {@link #ACTIVE_SESSION_CACHE_NAME}.
068 */
069 private String activeSessionsCacheName = ACTIVE_SESSION_CACHE_NAME;
070
071 /**
072 * Default no-arg constructor.
073 */
074 public CachingSessionDAO() {
075 }
076
077 /**
078 * Sets the cacheManager to use for acquiring the {@link #getActiveSessionsCache() activeSessionsCache} if
079 * one is not configured.
080 *
081 * @param cacheManager the manager to use for constructing the session cache.
082 */
083 public void setCacheManager(CacheManager cacheManager) {
084 this.cacheManager = cacheManager;
085 }
086
087 /**
088 * Returns the CacheManager to use for acquiring the {@link #getActiveSessionsCache() activeSessionsCache} if
089 * one is not configured. That is, the {@code CacheManager} will only be used if the
090 * {@link #getActiveSessionsCache() activeSessionsCache} property is {@code null}.
091 *
092 * @return the CacheManager used by the implementation that creates the activeSessions Cache.
093 */
094 public CacheManager getCacheManager() {
095 return cacheManager;
096 }
097
098 /**
099 * Returns the name of the actives sessions cache to be returned by the {@code CacheManager}. Unless
100 * overridden by {@link #setActiveSessionsCacheName(String)}, defaults to {@link #ACTIVE_SESSION_CACHE_NAME}.
101 *
102 * @return the name of the active sessions cache.
103 */
104 public String getActiveSessionsCacheName() {
105 return activeSessionsCacheName;
106 }
107
108 /**
109 * Sets the name of the active sessions cache to be returned by the {@code CacheManager}. Defaults to
110 * {@link #ACTIVE_SESSION_CACHE_NAME}.
111 *
112 * @param activeSessionsCacheName the name of the active sessions cache to be returned by the {@code CacheManager}.
113 */
114 public void setActiveSessionsCacheName(String activeSessionsCacheName) {
115 this.activeSessionsCacheName = activeSessionsCacheName;
116 }
117
118 /**
119 * Returns the cache instance to use for storing active sessions. If one is not available (it is {@code null}),
120 * it will be {@link CacheManager#getCache(String) acquired} from the {@link #setCacheManager configured}
121 * {@code CacheManager} using the {@link #getActiveSessionsCacheName() activeSessionsCacheName}.
122 *
123 * @return the cache instance to use for storing active sessions or {@code null} if the {@code Cache} instance
124 * should be retrieved from the
125 */
126 public Cache<Serializable, Session> getActiveSessionsCache() {
127 return this.activeSessions;
128 }
129
130 /**
131 * Sets the cache instance to use for storing active sessions. If one is not set (it remains {@code null}),
132 * it will be {@link CacheManager#getCache(String) acquired} from the {@link #setCacheManager configured}
133 * {@code CacheManager} using the {@link #getActiveSessionsCacheName() activeSessionsCacheName}.
134 *
135 * @param cache the cache instance to use for storing active sessions or {@code null} if the cache is to be
136 * acquired from the {@link #setCacheManager configured} {@code CacheManager}.
137 */
138 public void setActiveSessionsCache(Cache<Serializable, Session> cache) {
139 this.activeSessions = cache;
140 }
141
142 /**
143 * Returns the active sessions cache, but if that cache instance is null, first lazily creates the cache instance
144 * via the {@link #createActiveSessionsCache()} method and then returns the instance.
145 * <p/>
146 * Note that this method will only return a non-null value code if the {@code CacheManager} has been set. If
147 * not set, there will be no cache.
148 *
149 * @return the active sessions cache instance.
150 */
151 private Cache<Serializable, Session> getActiveSessionsCacheLazy() {
152 if (this.activeSessions == null) {
153 this.activeSessions = createActiveSessionsCache();
154 }
155 return activeSessions;
156 }
157
158 /**
159 * Creates a cache instance used to store active sessions. Creation is done by first
160 * {@link #getCacheManager() acquiring} the {@code CacheManager}. If the cache manager is not null, the
161 * cache returned is that resulting from the following call:
162 * <pre> String name = {@link #getActiveSessionsCacheName() getActiveSessionsCacheName()};
163 * cacheManager.getCache(name);</pre>
164 *
165 * @return a cache instance used to store active sessions, or {@code null} if the {@code CacheManager} has
166 * not been set.
167 */
168 protected Cache<Serializable, Session> createActiveSessionsCache() {
169 Cache<Serializable, Session> cache = null;
170 CacheManager mgr = getCacheManager();
171 if (mgr != null) {
172 String name = getActiveSessionsCacheName();
173 cache = mgr.getCache(name);
174 }
175 return cache;
176 }
177
178 /**
179 * Calls {@code super.create(session)}, then caches the session keyed by the returned {@code sessionId}, and then
180 * returns this {@code sessionId}.
181 *
182 * @param session Session object to create in the EIS and then cache.
183 */
184 public Serializable create(Session session) {
185 Serializable sessionId = super.create(session);
186 cache(session, sessionId);
187 return sessionId;
188 }
189
190 /**
191 * Returns the cached session with the corresponding {@code sessionId} or {@code null} if there is
192 * no session cached under that id (or if there is no Cache).
193 *
194 * @param sessionId the id of the cached session to acquire.
195 * @return the cached session with the corresponding {@code sessionId}, or {@code null} if the session
196 * does not exist or is not cached.
197 */
198 protected Session getCachedSession(Serializable sessionId) {
199 Session cached = null;
200 if (sessionId != null) {
201 Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
202 if (cache != null) {
203 cached = getCachedSession(sessionId, cache);
204 }
205 }
206 return cached;
207 }
208
209 /**
210 * Returns the Session with the specified id from the specified cache. This method simply calls
211 * {@code cache.get(sessionId)} and can be overridden by subclasses for custom acquisition behavior.
212 *
213 * @param sessionId the id of the session to acquire.
214 * @param cache the cache to acquire the session from
215 * @return the cached session, or {@code null} if the session wasn't in the cache.
216 */
217 protected Session getCachedSession(Serializable sessionId, Cache<Serializable, Session> cache) {
218 return cache.get(sessionId);
219 }
220
221 /**
222 * Caches the specified session under the cache entry key of {@code sessionId}.
223 *
224 * @param session the session to cache
225 * @param sessionId the session id, to be used as the cache entry key.
226 * @since 1.0
227 */
228 protected void cache(Session session, Serializable sessionId) {
229 if (session == null || sessionId == null) {
230 return;
231 }
232 Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
233 if (cache == null) {
234 return;
235 }
236 cache(session, sessionId, cache);
237 }
238
239 /**
240 * Caches the specified session in the given cache under the key of {@code sessionId}. This implementation
241 * simply calls {@code cache.put(sessionId,session)} and can be overridden for custom behavior.
242 *
243 * @param session the session to cache
244 * @param sessionId the id of the session, expected to be the cache key.
245 * @param cache the cache to store the session
246 */
247 protected void cache(Session session, Serializable sessionId, Cache<Serializable, Session> cache) {
248 cache.put(sessionId, session);
249 }
250
251 /**
252 * Attempts to acquire the Session from the cache first using the session ID as the cache key. If no session
253 * is found, {@code super.readSession(sessionId)} is called to perform the actual retrieval.
254 *
255 * @param sessionId the id of the session to retrieve from the EIS.
256 * @return the session identified by {@code sessionId} in the EIS.
257 * @throws UnknownSessionException if the id specified does not correspond to any session in the cache or EIS.
258 */
259 public Session readSession(Serializable sessionId) throws UnknownSessionException {
260 Session s = getCachedSession(sessionId);
261 if (s == null) {
262 s = super.readSession(sessionId);
263 }
264 return s;
265 }
266
267 /**
268 * Updates the state of the given session to the EIS by first delegating to
269 * {@link #doUpdate(org.apache.shiro.session.Session)}. If the session is a {@link ValidatingSession}, it will
270 * be added to the cache only if it is {@link ValidatingSession#isValid()} and if invalid, will be removed from the
271 * cache. If it is not a {@code ValidatingSession} instance, it will be added to the cache in any event.
272 *
273 * @param session the session object to update in the EIS.
274 * @throws UnknownSessionException if no existing EIS session record exists with the
275 * identifier of {@link Session#getId() session.getId()}
276 */
277 public void update(Session session) throws UnknownSessionException {
278 doUpdate(session);
279 if (session instanceof ValidatingSession) {
280 if (((ValidatingSession) session).isValid()) {
281 cache(session, session.getId());
282 } else {
283 uncache(session);
284 }
285 } else {
286 cache(session, session.getId());
287 }
288 }
289
290 /**
291 * Subclass implementation hook to actually persist the {@code Session}'s state to the underlying EIS.
292 *
293 * @param session the session object whose state will be propagated to the EIS.
294 */
295 protected abstract void doUpdate(Session session);
296
297 /**
298 * Removes the specified session from any cache and then permanently deletes the session from the EIS by
299 * delegating to {@link #doDelete}.
300 *
301 * @param session the session to remove from caches and permanently delete from the EIS.
302 */
303 public void delete(Session session) {
304 uncache(session);
305 doDelete(session);
306 }
307
308 /**
309 * Subclass implementation hook to permanently delete the given Session from the underlying EIS.
310 *
311 * @param session the session instance to permanently delete from the EIS.
312 */
313 protected abstract void doDelete(Session session);
314
315 /**
316 * Removes the specified Session from the cache.
317 *
318 * @param session the session to remove from the cache.
319 */
320 protected void uncache(Session session) {
321 if (session == null) {
322 return;
323 }
324 Serializable id = session.getId();
325 if (id == null) {
326 return;
327 }
328 Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
329 if (cache != null) {
330 cache.remove(id);
331 }
332 }
333
334 /**
335 * Returns all active sessions in the system.
336 * <p/>
337 * <p>This implementation merely returns the sessions found in the activeSessions cache. Subclass implementations
338 * may wish to override this method to retrieve them in a different way, perhaps by an RDBMS query or by other
339 * means.
340 *
341 * @return the sessions found in the activeSessions cache.
342 */
343 public Collection<Session> getActiveSessions() {
344 Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
345 if (cache != null) {
346 return cache.values();
347 } else {
348 return Collections.emptySet();
349 }
350 }
351 }