View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.session.mgt;
20  
21  import org.apache.shiro.cache.CacheManager;
22  import org.apache.shiro.cache.CacheManagerAware;
23  import org.apache.shiro.session.Session;
24  import org.apache.shiro.session.UnknownSessionException;
25  import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
26  import org.apache.shiro.session.mgt.eis.SessionDAO;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import java.io.Serializable;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.Date;
34  
35  /**
36   * Default business-tier implementation of a {@link ValidatingSessionManager}.  All session CRUD operations are
37   * delegated to an internal {@link SessionDAO}.
38   *
39   * @since 0.1
40   */
41  public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
42  
43      //TODO - complete JavaDoc
44  
45      private static final Logger log = LoggerFactory.getLogger(DefaultSessionManager.class);
46  
47      private SessionFactory sessionFactory;
48  
49      protected SessionDAO sessionDAO;  //todo - move SessionDAO up to AbstractValidatingSessionManager?
50  
51      private CacheManager cacheManager;
52  
53      private boolean deleteInvalidSessions;
54  
55      public DefaultSessionManager() {
56          this.deleteInvalidSessions = true;
57          this.sessionFactory = new SimpleSessionFactory();
58          this.sessionDAO = new MemorySessionDAO();
59      }
60  
61      public void setSessionDAO(SessionDAO sessionDAO) {
62          this.sessionDAO = sessionDAO;
63          applyCacheManagerToSessionDAO();
64      }
65  
66      public SessionDAO getSessionDAO() {
67          return this.sessionDAO;
68      }
69  
70      /**
71       * Returns the {@code SessionFactory} used to generate new {@link Session} instances.  The default instance
72       * is a {@link SimpleSessionFactory}.
73       *
74       * @return the {@code SessionFactory} used to generate new {@link Session} instances.
75       * @since 1.0
76       */
77      public SessionFactory getSessionFactory() {
78          return sessionFactory;
79      }
80  
81      /**
82       * Sets the {@code SessionFactory} used to generate new {@link Session} instances.  The default instance
83       * is a {@link SimpleSessionFactory}.
84       *
85       * @param sessionFactory the {@code SessionFactory} used to generate new {@link Session} instances.
86       * @since 1.0
87       */
88      public void setSessionFactory(SessionFactory sessionFactory) {
89          this.sessionFactory = sessionFactory;
90      }
91  
92      /**
93       * Returns {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
94       * {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control.  The
95       * default is {@code true} to ensure no orphans exist in the underlying data store.
96       * <h4>Usage</h4>
97       * It is ok to set this to {@code false} <b><em>ONLY</em></b> if you have some other process that you manage yourself
98       * that periodically deletes invalid sessions from the backing data store over time, such as via a Quartz or Cron
99       * job.  If you do not do this, the invalid sessions will become 'orphans' and fill up the data store over time.
100      * <p/>
101      * This property is provided because some systems need the ability to perform querying/reporting against sessions in
102      * the data store, even after they have stopped or expired.  Setting this attribute to {@code false} will allow
103      * such querying, but with the caveat that the application developer/configurer deletes the sessions themselves by
104      * some other means (cron, quartz, etc).
105      *
106      * @return {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
107      *         {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control.
108      * @since 1.0
109      */
110     public boolean isDeleteInvalidSessions() {
111         return deleteInvalidSessions;
112     }
113 
114     /**
115      * Sets whether or not sessions should be automatically deleted after they are discovered to be invalid.  Default
116      * value is {@code true} to ensure no orphans will exist in the underlying data store.
117      * <h4>WARNING</h4>
118      * Only set this value to {@code false} if you are manually going to delete sessions yourself by some process
119      * (quartz, cron, etc) external to Shiro's control.  See the
120      * {@link #isDeleteInvalidSessions() isDeleteInvalidSessions()} JavaDoc for more.
121      *
122      * @param deleteInvalidSessions whether or not sessions should be automatically deleted after they are discovered
123      *                              to be invalid.
124      * @since 1.0
125      */
126     @SuppressWarnings({"UnusedDeclaration"})
127     public void setDeleteInvalidSessions(boolean deleteInvalidSessions) {
128         this.deleteInvalidSessions = deleteInvalidSessions;
129     }
130 
131     public void setCacheManager(CacheManager cacheManager) {
132         this.cacheManager = cacheManager;
133         applyCacheManagerToSessionDAO();
134     }
135 
136     /**
137      * Sets the internal {@code CacheManager} on the {@code SessionDAO} if it implements the
138      * {@link org.apache.shiro.cache.CacheManagerAware CacheManagerAware} interface.
139      * <p/>
140      * This method is called after setting a cacheManager via the
141      * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) setCacheManager} method <em>em</em> when
142      * setting a {@code SessionDAO} via the {@link #setSessionDAO} method to allow it to be propagated
143      * in either case.
144      *
145      * @since 1.0
146      */
147     private void applyCacheManagerToSessionDAO() {
148         if (this.cacheManager != null && this.sessionDAO != null && this.sessionDAO instanceof CacheManagerAware) {
149             ((CacheManagerAware) this.sessionDAO).setCacheManager(this.cacheManager);
150         }
151     }
152 
153     protected Session doCreateSession(SessionContext context) {
154         Session s = newSessionInstance(context);
155         if (log.isTraceEnabled()) {
156             log.trace("Creating session for host {}", s.getHost());
157         }
158         create(s);
159         return s;
160     }
161 
162     protected Session newSessionInstance(SessionContext context) {
163         return getSessionFactory().createSession(context);
164     }
165 
166     /**
167      * Persists the given session instance to an underlying EIS (Enterprise Information System).  This implementation
168      * delegates and calls
169      * <code>this.{@link SessionDAO sessionDAO}.{@link SessionDAO#create(org.apache.shiro.session.Session) create}(session);<code>
170      *
171      * @param session the Session instance to persist to the underlying EIS.
172      */
173     protected void create(Session session) {
174         if (log.isDebugEnabled()) {
175             log.debug("Creating new EIS record for new session instance [" + session + "]");
176         }
177         sessionDAO.create(session);
178     }
179 
180     @Override
181     protected void onStop(Session session) {
182         if (session instanceof SimpleSession) {
183             SimpleSession ss = (SimpleSession) session;
184             Date stopTs = ss.getStopTimestamp();
185             ss.setLastAccessTime(stopTs);
186         }
187         onChange(session);
188     }
189 
190     @Override
191     protected void afterStopped(Session session) {
192         if (isDeleteInvalidSessions()) {
193             delete(session);
194         }
195     }
196 
197     protected void onExpiration(Session session) {
198         if (session instanceof SimpleSession) {
199             ((SimpleSession) session).setExpired(true);
200         }
201         onChange(session);
202     }
203 
204     @Override
205     protected void afterExpired(Session session) {
206         if (isDeleteInvalidSessions()) {
207             delete(session);
208         }
209     }
210 
211     protected void onChange(Session session) {
212         sessionDAO.update(session);
213     }
214 
215     protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
216         Serializable sessionId = getSessionId(sessionKey);
217         if (sessionId == null) {
218             log.debug("Unable to resolve session ID from SessionKey [{}].  Returning null to indicate a " +
219                     "session could not be found.", sessionKey);
220             return null;
221         }
222         Session s = retrieveSessionFromDataSource(sessionId);
223         if (s == null) {
224             //session ID was provided, meaning one is expected to be found, but we couldn't find one:
225             String msg = "Could not find session with ID [" + sessionId + "]";
226             throw new UnknownSessionException(msg);
227         }
228         return s;
229     }
230 
231     protected Serializable getSessionId(SessionKey sessionKey) {
232         return sessionKey.getSessionId();
233     }
234 
235     protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
236         return sessionDAO.readSession(sessionId);
237     }
238 
239     protected void delete(Session session) {
240         sessionDAO.delete(session);
241     }
242 
243     protected Collection<Session> getActiveSessions() {
244         Collection<Session> active = sessionDAO.getActiveSessions();
245         return active != null ? active : Collections.<Session>emptySet();
246     }
247 
248 }