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}