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.authz.AuthorizationException;
22  import org.apache.shiro.session.ExpiredSessionException;
23  import org.apache.shiro.session.InvalidSessionException;
24  import org.apache.shiro.session.Session;
25  import org.apache.shiro.session.UnknownSessionException;
26  import org.apache.shiro.util.Destroyable;
27  import org.apache.shiro.util.LifecycleUtils;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  import java.util.Collection;
32  
33  
34  /**
35   * Default business-tier implementation of the {@link ValidatingSessionManager} interface.
36   *
37   * @since 0.1
38   */
39  public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager
40          implements ValidatingSessionManager, Destroyable {
41  
42      //TODO - complete JavaDoc
43  
44      private static final Logger log = LoggerFactory.getLogger(AbstractValidatingSessionManager.class);
45  
46      /**
47       * The default interval at which sessions will be validated (1 hour);
48       * This can be overridden by calling {@link #setSessionValidationInterval(long)}
49       */
50      public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = MILLIS_PER_HOUR;
51  
52      protected boolean sessionValidationSchedulerEnabled;
53  
54      /**
55       * Scheduler used to validate sessions on a regular basis.
56       */
57      protected SessionValidationScheduler sessionValidationScheduler;
58  
59      protected long sessionValidationInterval;
60  
61      public AbstractValidatingSessionManager() {
62          this.sessionValidationSchedulerEnabled = true;
63          this.sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL;
64      }
65  
66      public boolean isSessionValidationSchedulerEnabled() {
67          return sessionValidationSchedulerEnabled;
68      }
69  
70      @SuppressWarnings({"UnusedDeclaration"})
71      public void setSessionValidationSchedulerEnabled(boolean sessionValidationSchedulerEnabled) {
72          this.sessionValidationSchedulerEnabled = sessionValidationSchedulerEnabled;
73      }
74  
75      public void setSessionValidationScheduler(SessionValidationScheduler sessionValidationScheduler) {
76          this.sessionValidationScheduler = sessionValidationScheduler;
77      }
78  
79      public SessionValidationScheduler getSessionValidationScheduler() {
80          return sessionValidationScheduler;
81      }
82  
83      private void enableSessionValidationIfNecessary() {
84          SessionValidationScheduler scheduler = getSessionValidationScheduler();
85          if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) {
86              enableSessionValidation();
87          }
88      }
89  
90      /**
91       * If using the underlying default <tt>SessionValidationScheduler</tt> (that is, the
92       * {@link #setSessionValidationScheduler(SessionValidationScheduler) setSessionValidationScheduler} method is
93       * never called) , this method allows one to specify how
94       * frequently session should be validated (to check for orphans).  The default value is
95       * {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
96       * <p/>
97       * If you override the default scheduler, it is assumed that overriding instance 'knows' how often to
98       * validate sessions, and this attribute will be ignored.
99       * <p/>
100      * Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
101      *
102      * @param sessionValidationInterval the time in milliseconds between checking for valid sessions to reap orphans.
103      */
104     public void setSessionValidationInterval(long sessionValidationInterval) {
105         this.sessionValidationInterval = sessionValidationInterval;
106     }
107 
108     public long getSessionValidationInterval() {
109         return sessionValidationInterval;
110     }
111 
112     @Override
113     protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
114         enableSessionValidationIfNecessary();
115 
116         log.trace("Attempting to retrieve session with key {}", key);
117 
118         Session s = retrieveSession(key);
119         if (s != null) {
120             validate(s, key);
121         }
122         return s;
123     }
124 
125     /**
126      * Looks up a session from the underlying data store based on the specified session key.
127      *
128      * @param key the session key to use to look up the target session.
129      * @return the session identified by {@code sessionId}.
130      * @throws UnknownSessionException if there is no session identified by {@code sessionId}.
131      */
132     protected abstract Session retrieveSession(SessionKey key) throws UnknownSessionException;
133 
134     protected Session createSession(SessionContext context) throws AuthorizationException {
135         enableSessionValidationIfNecessary();
136         return doCreateSession(context);
137     }
138 
139     protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException;
140 
141     protected void validate(Session session, SessionKey key) throws InvalidSessionException {
142         try {
143             doValidate(session);
144         } catch (ExpiredSessionException ese) {
145             onExpiration(session, ese, key);
146             throw ese;
147         } catch (InvalidSessionException ise) {
148             onInvalidation(session, ise, key);
149             throw ise;
150         }
151     }
152 
153     protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
154         log.trace("Session with id [{}] has expired.", s.getId());
155         try {
156             onExpiration(s);
157             notifyExpiration(s);
158         } finally {
159             afterExpired(s);
160         }
161     }
162 
163     protected void onExpiration(Session session) {
164         onChange(session);
165     }
166 
167     protected void afterExpired(Session session) {
168     }
169 
170     protected void onInvalidation(Session s, InvalidSessionException ise, SessionKey key) {
171         if (ise instanceof ExpiredSessionException) {
172             onExpiration(s, (ExpiredSessionException) ise, key);
173             return;
174         }
175         log.trace("Session with id [{}] is invalid.", s.getId());
176         try {
177             onStop(s);
178             notifyStop(s);
179         } finally {
180             afterStopped(s);
181         }
182     }
183 
184     protected void doValidate(Session session) throws InvalidSessionException {
185         if (session instanceof ValidatingSession) {
186             ((ValidatingSession) session).validate();
187         } else {
188             String msg = "The " + getClass().getName() + " implementation only supports validating " +
189                     "Session implementations of the " + ValidatingSession.class.getName() + " interface.  " +
190                     "Please either implement this interface in your session implementation or override the " +
191                     AbstractValidatingSessionManager.class.getName() + ".doValidate(Session) method to perform validation.";
192             throw new IllegalStateException(msg);
193         }
194     }
195 
196     /**
197      * Subclass template hook in case per-session timeout is not based on
198      * {@link org.apache.shiro.session.Session#getTimeout()}.
199      * <p/>
200      * <p>This implementation merely returns {@link org.apache.shiro.session.Session#getTimeout()}</p>
201      *
202      * @param session the session for which to determine session timeout.
203      * @return the time in milliseconds the specified session may remain idle before expiring.
204      */
205     protected long getTimeout(Session session) {
206         return session.getTimeout();
207     }
208 
209     protected SessionValidationScheduler createSessionValidationScheduler() {
210         ExecutorServiceSessionValidationScheduler scheduler;
211 
212         if (log.isDebugEnabled()) {
213             log.debug("No sessionValidationScheduler set.  Attempting to create default instance.");
214         }
215         scheduler = new ExecutorServiceSessionValidationScheduler(this);
216         scheduler.setInterval(getSessionValidationInterval());
217         if (log.isTraceEnabled()) {
218             log.trace("Created default SessionValidationScheduler instance of type [" + scheduler.getClass().getName() + "].");
219         }
220         return scheduler;
221     }
222 
223     protected synchronized void enableSessionValidation() {
224         SessionValidationScheduler scheduler = getSessionValidationScheduler();
225         if (scheduler == null) {
226             scheduler = createSessionValidationScheduler();
227             setSessionValidationScheduler(scheduler);
228         }
229         // it is possible that that a scheduler was already created and set via 'setSessionValidationScheduler()'
230         // but would not have been enabled/started yet
231         if (!scheduler.isEnabled()) {
232             if (log.isInfoEnabled()) {
233                 log.info("Enabling session validation scheduler...");
234             }
235             scheduler.enableSessionValidation();
236             afterSessionValidationEnabled();
237         }
238     }
239 
240     protected void afterSessionValidationEnabled() {
241     }
242 
243     protected synchronized void disableSessionValidation() {
244         beforeSessionValidationDisabled();
245         SessionValidationScheduler scheduler = getSessionValidationScheduler();
246         if (scheduler != null) {
247             try {
248                 scheduler.disableSessionValidation();
249                 if (log.isInfoEnabled()) {
250                     log.info("Disabled session validation scheduler.");
251                 }
252             } catch (Exception e) {
253                 if (log.isDebugEnabled()) {
254                     String msg = "Unable to disable SessionValidationScheduler.  Ignoring (shutting down)...";
255                     log.debug(msg, e);
256                 }
257             }
258             LifecycleUtils.destroy(scheduler);
259             setSessionValidationScheduler(null);
260         }
261     }
262 
263     protected void beforeSessionValidationDisabled() {
264     }
265 
266     public void destroy() {
267         disableSessionValidation();
268     }
269 
270     /**
271      * @see ValidatingSessionManager#validateSessions()
272      */
273     public void validateSessions() {
274         if (log.isInfoEnabled()) {
275             log.info("Validating all active sessions...");
276         }
277 
278         int invalidCount = 0;
279 
280         Collection<Session> activeSessions = getActiveSessions();
281 
282         if (activeSessions != null && !activeSessions.isEmpty()) {
283             for (Session s : activeSessions) {
284                 try {
285                     //simulate a lookup key to satisfy the method signature.
286                     //this could probably stand to be cleaned up in future versions:
287                     SessionKey key = new DefaultSessionKey(s.getId());
288                     validate(s, key);
289                 } catch (InvalidSessionException e) {
290                     if (log.isDebugEnabled()) {
291                         boolean expired = (e instanceof ExpiredSessionException);
292                         String msg = "Invalidated session with id [" + s.getId() + "]" +
293                                 (expired ? " (expired)" : " (stopped)");
294                         log.debug(msg);
295                     }
296                     invalidCount++;
297                 }
298             }
299         }
300 
301         if (log.isInfoEnabled()) {
302             String msg = "Finished session validation.";
303             if (invalidCount > 0) {
304                 msg += "  [" + invalidCount + "] sessions were stopped.";
305             } else {
306                 msg += "  No sessions were stopped.";
307             }
308             log.info(msg);
309         }
310     }
311 
312     protected abstract Collection<Session> getActiveSessions();
313 }