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 */ 019package org.apache.shiro.session.mgt.eis; 020 021import org.apache.shiro.cache.Cache; 022import org.apache.shiro.cache.CacheManager; 023import org.apache.shiro.cache.CacheManagerAware; 024import org.apache.shiro.session.Session; 025import org.apache.shiro.session.UnknownSessionException; 026import org.apache.shiro.session.mgt.ValidatingSession; 027 028import java.io.Serializable; 029import java.util.Collection; 030import 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 * @since 0.2 047 */ 048public abstract class CachingSessionDAO extends AbstractSessionDAO implements CacheManagerAware { 049 050 /** 051 * The default active sessions cache name, equal to {@code shiro-activeSessionCache}. 052 */ 053 public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache"; 054 055 /** 056 * The CacheManager to use to acquire the Session cache. 057 */ 058 private CacheManager cacheManager; 059 060 /** 061 * The Cache instance responsible for caching Sessions. 062 */ 063 private Cache<Serializable, Session> activeSessions; 064 065 /** 066 * The name of the session cache, defaults to {@link #ACTIVE_SESSION_CACHE_NAME}. 067 */ 068 private String activeSessionsCacheName = ACTIVE_SESSION_CACHE_NAME; 069 070 /** 071 * Default no-arg constructor. 072 */ 073 public CachingSessionDAO() { 074 } 075 076 /** 077 * Sets the cacheManager to use for acquiring the {@link #getActiveSessionsCache() activeSessionsCache} if 078 * one is not configured. 079 * 080 * @param cacheManager the manager to use for constructing the session cache. 081 */ 082 public void setCacheManager(CacheManager cacheManager) { 083 this.cacheManager = cacheManager; 084 } 085 086 /** 087 * Returns the CacheManager to use for acquiring the {@link #getActiveSessionsCache() activeSessionsCache} if 088 * one is not configured. That is, the {@code CacheManager} will only be used if the 089 * {@link #getActiveSessionsCache() activeSessionsCache} property is {@code null}. 090 * 091 * @return the CacheManager used by the implementation that creates the activeSessions Cache. 092 */ 093 public CacheManager getCacheManager() { 094 return cacheManager; 095 } 096 097 /** 098 * Returns the name of the actives sessions cache to be returned by the {@code CacheManager}. Unless 099 * 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}