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