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     * @since 0.2
047     */
048    public 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    }