blob: d31c5d362b79c1179792fc0013e389b4a7849545 [file] [log] [blame]
/*
* Copyright 2005-2008 Les Hazlewood, Jeremy Haile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jsecurity.session.mgt;
import org.jsecurity.session.ExpiredSessionException;
import org.jsecurity.session.InvalidSessionException;
import org.jsecurity.session.Session;
import org.jsecurity.util.LifecycleUtils;
import java.io.Serializable;
import java.text.DateFormat;
import java.util.Collection;
import java.util.Date;
/**
* Default business-tier implementation of the {@link ValidatingSessionManager} interface.
*
* @author Les Hazlewood
* @author Jeremy Haile
* @since 0.1
*/
public abstract class AbstractValidatingSessionManager extends AbstractSessionManager
implements ValidatingSessionManager {
protected static final long MILLIS_PER_SECOND = 1000;
protected static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
private static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
/** Default main session timeout value (30 * 60 * 1000 milliseconds = 30 minutes). */
public static final long DEFAULT_GLOBAL_SESSION_TIMEOUT = 30 * MILLIS_PER_MINUTE;
/**
* The default interval at which sessions will be validated (1 hour);
* This can be overridden by calling {@link #setSessionValidationInterval(long)}
*/
public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = MILLIS_PER_HOUR;
protected boolean sessionValidationSchedulerEnabled = true; //default
/**
* Scheduler used to validate sessions on a regular basis.
*/
protected SessionValidationScheduler sessionValidationScheduler = null;
protected long sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL;
protected long globalSessionTimeout = DEFAULT_GLOBAL_SESSION_TIMEOUT;
public AbstractValidatingSessionManager() {
}
public boolean isSessionValidationSchedulerEnabled() {
return sessionValidationSchedulerEnabled;
}
public void setSessionValidationSchedulerEnabled(boolean sessionValidationSchedulerEnabled) {
this.sessionValidationSchedulerEnabled = sessionValidationSchedulerEnabled;
}
public void setSessionValidationScheduler( SessionValidationScheduler sessionValidationScheduler ) {
this.sessionValidationScheduler = sessionValidationScheduler;
}
public SessionValidationScheduler getSessionValidationScheduler() {
return sessionValidationScheduler;
}
/**
* Returns the time in milliseconds that any session may remain idle before expiring. This
* value is just a main default for all sessions and may be overridden by subclasses on a
* <em>per-session</em> basis by overriding the {@link #getTimeout(Session)} method if
* so desired.
*
* <ul>
* <li>A negative return value means sessions never expire.</li>
* <li>A non-negative return value (0 or greater) means session timeout will occur as expected.</li>
* </ul>
*
* <p>Unless overridden via the {@link #setGlobalSessionTimeout} method, the default value is
* {@link #DEFAULT_GLOBAL_SESSION_TIMEOUT}.
*
* @return the time in milliseconds that any session may remain idle before expiring.
*/
public long getGlobalSessionTimeout() {
return globalSessionTimeout;
}
/**
* Sets the time in milliseconds that any session may remain idle before expiring. This
* value is just a main default for all sessions. Subclasses may override the
* {@link #getTimeout} method to determine time-out values on a <em>per-session</em> basis.
*
* @param globalSessionTimeout the time in milliseconds any session may remain idle before
* expiring.
*/
public void setGlobalSessionTimeout( int globalSessionTimeout ) {
this.globalSessionTimeout = globalSessionTimeout;
}
/**
* If using the underlying default <tt>SessionValidationScheduler</tt> (that is, the
* {@link #setSessionValidationScheduler(SessionValidationScheduler) setSessionValidationScheduler} method is
* never called) , this method allows one to specify how
* frequently session should be validated (to check for orphans). The default value is
* {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
*
* <p>If you override the default scheduler, it is assumed that overriding instance 'knows' how often to
* validate sessions, and this attribute will be ignored.
*
* <p>Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
* @param sessionValidationInterval the time in milliseconds between checking for valid sessions to reap orphans.
*/
public void setSessionValidationInterval( long sessionValidationInterval ) {
this.sessionValidationInterval = sessionValidationInterval;
}
public long getSessionValidationInterval() {
return sessionValidationInterval;
}
protected void validate(Session session) throws InvalidSessionException {
if (isExpired(session)) {
//update EIS entry if it hasn't been updated already:
if (!session.isExpired()) {
expire(session);
}
//throw an exception explaining details of why it expired:
Date lastAccessTime = session.getLastAccessTime();
long timeout = getTimeout(session);
Serializable sessionId = session.getId();
DateFormat df = DateFormat.getInstance();
String msg = "Session with id [" + sessionId + "] has expired. " +
"Last access time: " + df.format(lastAccessTime) +
". Current time: " + df.format(new Date()) +
". Session timeout is set to " + timeout / MILLIS_PER_SECOND + " seconds (" +
timeout / MILLIS_PER_MINUTE + " minutes)";
if (log.isTraceEnabled()) {
log.trace(msg);
}
throw new ExpiredSessionException(msg, sessionId);
}
//check for stopped (but not expired):
if (session.getStopTimestamp() != null) {
//destroy timestamp is set, so the session is considered stopped:
String msg = "Session with id [" + session.getId() + "] has been " +
"explicitly stopped. No further interaction under this session is " +
"allowed.";
throw new InvalidSessionException(msg, session.getId());
}
}
/**
* Determines if the specified session is expired.
*
* @param session the persistent pojo Session implementation to check for expiration.
* @return true if the specified session has expired, false otherwise.
*/
protected boolean isExpired(Session session) {
//If the EIS data has already been set as expired, return true:
//WARNING: This will cause an infinite loop if the session argument is a proxy back
//to this instance (e.g. as would be the case if passing in a DelegatingSession instace.
//To be safe, make sure the argument is representative of EIS data and
//the isExpired method returns a boolean class attribute and does not call another object.
if (session.isExpired()) {
return true;
}
if (isExpirationEnabled(session)) {
long timeout = getTimeout(session);
if (timeout >= 0l) {
Date lastAccessTime = session.getLastAccessTime();
if (lastAccessTime == null) {
String msg = "session.lastAccessTime for session with id [" +
session.getId() + "] is null. This value must be set at " +
"least once. Please check the " +
session.getClass().getName() + " implementation and ensure " +
"this value will be set (perhaps in the constructor?)";
throw new IllegalStateException(msg);
}
// Calculate at what time a session would have been last accessed
// for it to be expired at this point. In other words, subtract
// from the current time the amount of time that a session can
// be inactive before expiring. If the session was last accessed
// before this time, it is expired.
long expireTimeMillis = System.currentTimeMillis() - timeout;
Date expireTime = new Date(expireTimeMillis);
return lastAccessTime.before(expireTime);
} else {
if (log.isTraceEnabled()) {
log.trace("No timeout for session with id [" + session.getId() +
"]. Session is not considered expired.");
}
}
} else {
if (log.isTraceEnabled()) {
log.trace("Time-out is disabled for Session with id [" + session.getId() +
"]. Session is not expired.");
}
}
return false;
}
/**
* Returns whether or not a particular session can expire.
*
* <p>Default implementation always returns <tt>true</tt>.
*
* <p>Overriding this method can be particularly useful in some circumstances. For example,
* daemon users (background process users) can be configured in a system like any other user.
* It is much easier to define a daemon account and use the same session and security framework
* that supports normal human users, rather than program special-case logic. Daemon accounts
* are often expected to interact with the system at any time, regardless of (in)activity.
* This method provides a means to disable session expiration in such cases.
*
* <p>Most overriding implementations usually infer a user or user id from the specified
* <tt>Session</tt> and determine per-user timeout settings in a specific manner.
*
* @param session the session for which to determine if timeout expiration is enabled.
* @return true if expiration is enabled for the specified session, false otherwise.
*/
protected boolean isExpirationEnabled(Session session) {
return getTimeout(session) >= 0l;
}
/**
* Subclass template hook in case per-session timeout is not based on
* {@link org.jsecurity.session.Session#getTimeout()}.
*
* <p>This implementation merely returns {@link org.jsecurity.session.Session#getTimeout()}</p>
*
* @param session the session for which to determine session timeout.
* @return the time in milliseconds the specified session may remain idle before expiring.
*/
protected long getTimeout(Session session) {
return session.getTimeout();
}
protected SessionValidationScheduler createSessionValidationScheduler() {
SessionValidationScheduler scheduler;
if ( log.isDebugEnabled() ) {
log.debug( "No sessionValidationScheduler set. Attempting to create default instance." );
}
scheduler = new ExecutorServiceSessionValidationScheduler( this );
((ExecutorServiceSessionValidationScheduler)scheduler).setInterval(getSessionValidationInterval());
if ( log.isTraceEnabled() ) {
log.trace( "Created default SessionValidationScheduler instance of type [" + scheduler.getClass().getName() + "]." );
}
return scheduler;
}
protected void startSessionValidation() {
SessionValidationScheduler scheduler = getSessionValidationScheduler();
if ( scheduler == null ) {
scheduler = createSessionValidationScheduler();
setSessionValidationScheduler( scheduler );
}
if ( log.isInfoEnabled() ) {
log.info( "Starting session validation scheduler..." );
}
scheduler.startSessionValidation();
}
protected void stopSessionValidation() {
SessionValidationScheduler scheduler = getSessionValidationScheduler();
if ( scheduler != null ) {
try {
scheduler.stopSessionValidation();
} catch ( Exception e ) {
if ( log.isDebugEnabled() ) {
String msg = "Unable to stop SessionValidationScheduler. Ignoring (shutting down)...";
log.debug( msg, e );
}
}
LifecycleUtils.destroy(scheduler);
setSessionValidationScheduler(null);
}
}
public void init() {
if (isSessionValidationSchedulerEnabled()) {
startSessionValidation();
}
afterSessionValidationStarted();
}
protected void afterSessionValidationStarted(){}
public void destroy() {
beforeSessionValidationStopped();
stopSessionValidation();
}
protected void beforeSessionValidationStopped(){}
/**
* @see ValidatingSessionManager#validateSessions()
*/
public void validateSessions() {
if ( log.isInfoEnabled() ) {
log.info( "Validating all active sessions..." );
}
int invalidCount = 0;
Collection<Session> activeSessions = getActiveSessions();
if ( activeSessions != null && !activeSessions.isEmpty() ) {
for ( Session s : activeSessions ) {
try {
validate( s );
} catch ( InvalidSessionException e ) {
if ( log.isDebugEnabled() ) {
boolean expired = ( e instanceof ExpiredSessionException );
String msg = "Invalidated session with id [" + s.getId() + "]" +
( expired ? " (expired)" : " (stopped)" );
log.debug( msg );
}
invalidCount++;
}
}
}
if ( log.isInfoEnabled() ) {
String msg = "Finished session validation.";
if ( invalidCount > 0 ) {
msg += " [" + invalidCount + "] sessions were stopped.";
} else {
msg += " No sessions were stopped.";
}
log.info( msg );
}
}
protected abstract Collection<Session> getActiveSessions();
public void validateSession( Serializable sessionId ) {
//standard getSession call will validate, so just call the method:
getSession(sessionId);
}
}