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    }