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;
020
021 import org.apache.shiro.authz.AuthorizationException;
022 import org.apache.shiro.session.ExpiredSessionException;
023 import org.apache.shiro.session.InvalidSessionException;
024 import org.apache.shiro.session.Session;
025 import org.apache.shiro.session.UnknownSessionException;
026 import org.apache.shiro.util.Destroyable;
027 import org.apache.shiro.util.LifecycleUtils;
028 import org.slf4j.Logger;
029 import org.slf4j.LoggerFactory;
030
031 import java.util.Collection;
032
033
034 /**
035 * Default business-tier implementation of the {@link ValidatingSessionManager} interface.
036 *
037 * @author Les Hazlewood
038 * @author Jeremy Haile
039 * @since 0.1
040 */
041 public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager
042 implements ValidatingSessionManager, Destroyable {
043
044 //TODO - complete JavaDoc
045
046 private static final Logger log = LoggerFactory.getLogger(AbstractValidatingSessionManager.class);
047
048 /**
049 * The default interval at which sessions will be validated (1 hour);
050 * This can be overridden by calling {@link #setSessionValidationInterval(long)}
051 */
052 public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = MILLIS_PER_HOUR;
053
054 protected boolean sessionValidationSchedulerEnabled;
055
056 /**
057 * Scheduler used to validate sessions on a regular basis.
058 */
059 protected SessionValidationScheduler sessionValidationScheduler;
060
061 protected long sessionValidationInterval;
062
063 public AbstractValidatingSessionManager() {
064 this.sessionValidationSchedulerEnabled = true;
065 this.sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL;
066 }
067
068 public boolean isSessionValidationSchedulerEnabled() {
069 return sessionValidationSchedulerEnabled;
070 }
071
072 @SuppressWarnings({"UnusedDeclaration"})
073 public void setSessionValidationSchedulerEnabled(boolean sessionValidationSchedulerEnabled) {
074 this.sessionValidationSchedulerEnabled = sessionValidationSchedulerEnabled;
075 }
076
077 public void setSessionValidationScheduler(SessionValidationScheduler sessionValidationScheduler) {
078 this.sessionValidationScheduler = sessionValidationScheduler;
079 }
080
081 public SessionValidationScheduler getSessionValidationScheduler() {
082 return sessionValidationScheduler;
083 }
084
085 private void enableSessionValidationIfNecessary() {
086 SessionValidationScheduler scheduler = getSessionValidationScheduler();
087 if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) {
088 enableSessionValidation();
089 }
090 }
091
092 /**
093 * If using the underlying default <tt>SessionValidationScheduler</tt> (that is, the
094 * {@link #setSessionValidationScheduler(SessionValidationScheduler) setSessionValidationScheduler} method is
095 * never called) , this method allows one to specify how
096 * frequently session should be validated (to check for orphans). The default value is
097 * {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
098 * <p/>
099 * If you override the default scheduler, it is assumed that overriding instance 'knows' how often to
100 * validate sessions, and this attribute will be ignored.
101 * <p/>
102 * Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
103 *
104 * @param sessionValidationInterval the time in milliseconds between checking for valid sessions to reap orphans.
105 */
106 public void setSessionValidationInterval(long sessionValidationInterval) {
107 this.sessionValidationInterval = sessionValidationInterval;
108 }
109
110 public long getSessionValidationInterval() {
111 return sessionValidationInterval;
112 }
113
114 @Override
115 protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
116 enableSessionValidationIfNecessary();
117
118 log.trace("Attempting to retrieve session with key {}", key);
119
120 Session s = retrieveSession(key);
121 if (s != null) {
122 validate(s, key);
123 }
124 return s;
125 }
126
127 /**
128 * Looks up a session from the underlying data store based on the specified session key.
129 *
130 * @param key the session key to use to look up the target session.
131 * @return the session identified by {@code sessionId}.
132 * @throws UnknownSessionException if there is no session identified by {@code sessionId}.
133 */
134 protected abstract Session retrieveSession(SessionKey key) throws UnknownSessionException;
135
136 protected Session createSession(SessionContext context) throws AuthorizationException {
137 enableSessionValidationIfNecessary();
138 return doCreateSession(context);
139 }
140
141 protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException;
142
143 protected void validate(Session session, SessionKey key) throws InvalidSessionException {
144 try {
145 doValidate(session);
146 } catch (ExpiredSessionException ese) {
147 onExpiration(session, ese, key);
148 throw ese;
149 } catch (InvalidSessionException ise) {
150 onInvalidation(session, ise, key);
151 throw ise;
152 }
153 }
154
155 protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
156 log.trace("Session with id [{}] has expired.", s.getId());
157 onExpiration(s);
158 notifyExpiration(s);
159 afterExpired(s);
160 }
161
162 protected void onExpiration(Session session) {
163 onChange(session);
164 }
165
166 protected void afterExpired(Session session) {
167 }
168
169 protected void onInvalidation(Session s, InvalidSessionException ise, SessionKey key) {
170 if (ise instanceof ExpiredSessionException) {
171 onExpiration(s, (ExpiredSessionException) ise, key);
172 return;
173 }
174 log.trace("Session with id [{}] is invalid.", s.getId());
175 onStop(s);
176 notifyStop(s);
177 afterStopped(s);
178 }
179
180 protected void doValidate(Session session) throws InvalidSessionException {
181 if (session instanceof ValidatingSession) {
182 ((ValidatingSession) session).validate();
183 } else {
184 String msg = "The " + getClass().getName() + " implementation only supports validating " +
185 "Session implementations of the " + ValidatingSession.class.getName() + " interface. " +
186 "Please either implement this interface in your session implementation or override the " +
187 AbstractValidatingSessionManager.class.getName() + ".doValidate(Session) method to perform validation.";
188 throw new IllegalStateException(msg);
189 }
190 }
191
192 /**
193 * Subclass template hook in case per-session timeout is not based on
194 * {@link org.apache.shiro.session.Session#getTimeout()}.
195 * <p/>
196 * <p>This implementation merely returns {@link org.apache.shiro.session.Session#getTimeout()}</p>
197 *
198 * @param session the session for which to determine session timeout.
199 * @return the time in milliseconds the specified session may remain idle before expiring.
200 */
201 protected long getTimeout(Session session) {
202 return session.getTimeout();
203 }
204
205 protected SessionValidationScheduler createSessionValidationScheduler() {
206 ExecutorServiceSessionValidationScheduler scheduler;
207
208 if (log.isDebugEnabled()) {
209 log.debug("No sessionValidationScheduler set. Attempting to create default instance.");
210 }
211 scheduler = new ExecutorServiceSessionValidationScheduler(this);
212 scheduler.setInterval(getSessionValidationInterval());
213 if (log.isTraceEnabled()) {
214 log.trace("Created default SessionValidationScheduler instance of type [" + scheduler.getClass().getName() + "].");
215 }
216 return scheduler;
217 }
218
219 protected void enableSessionValidation() {
220 SessionValidationScheduler scheduler = getSessionValidationScheduler();
221 if (scheduler == null) {
222 scheduler = createSessionValidationScheduler();
223 setSessionValidationScheduler(scheduler);
224 }
225 if (log.isInfoEnabled()) {
226 log.info("Enabling session validation scheduler...");
227 }
228 scheduler.enableSessionValidation();
229 afterSessionValidationEnabled();
230 }
231
232 protected void afterSessionValidationEnabled() {
233 }
234
235 protected void disableSessionValidation() {
236 beforeSessionValidationDisabled();
237 SessionValidationScheduler scheduler = getSessionValidationScheduler();
238 if (scheduler != null) {
239 try {
240 scheduler.disableSessionValidation();
241 if (log.isInfoEnabled()) {
242 log.info("Disabled session validation scheduler.");
243 }
244 } catch (Exception e) {
245 if (log.isDebugEnabled()) {
246 String msg = "Unable to disable SessionValidationScheduler. Ignoring (shutting down)...";
247 log.debug(msg, e);
248 }
249 }
250 LifecycleUtils.destroy(scheduler);
251 setSessionValidationScheduler(null);
252 }
253 }
254
255 protected void beforeSessionValidationDisabled() {
256 }
257
258 public void destroy() {
259 disableSessionValidation();
260 }
261
262 /**
263 * @see ValidatingSessionManager#validateSessions()
264 */
265 public void validateSessions() {
266 if (log.isInfoEnabled()) {
267 log.info("Validating all active sessions...");
268 }
269
270 int invalidCount = 0;
271
272 Collection<Session> activeSessions = getActiveSessions();
273
274 if (activeSessions != null && !activeSessions.isEmpty()) {
275 for (Session s : activeSessions) {
276 try {
277 doValidate(s);
278 } catch (InvalidSessionException e) {
279 if (log.isDebugEnabled()) {
280 boolean expired = (e instanceof ExpiredSessionException);
281 String msg = "Invalidated session with id [" + s.getId() + "]" +
282 (expired ? " (expired)" : " (stopped)");
283 log.debug(msg);
284 }
285 invalidCount++;
286 }
287 }
288 }
289
290 if (log.isInfoEnabled()) {
291 String msg = "Finished session validation.";
292 if (invalidCount > 0) {
293 msg += " [" + invalidCount + "] sessions were stopped.";
294 } else {
295 msg += " No sessions were stopped.";
296 }
297 log.info(msg);
298 }
299 }
300
301 protected abstract Collection<Session> getActiveSessions();
302 }