/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 com.hwlcn.security.session.mgt; import com.hwlcn.security.authz.AuthorizationException; import com.hwlcn.security.session.ExpiredSessionException; import com.hwlcn.security.session.InvalidSessionException; import com.hwlcn.security.session.Session; import com.hwlcn.security.util.Destroyable; import com.hwlcn.security.util.LifecycleUtils; import com.hwlcn.security.session.UnknownSessionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; /** * Default business-tier implementation of the {@link ValidatingSessionManager} interface. * * @since 0.1 */ public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager implements ValidatingSessionManager, Destroyable { //TODO - complete JavaDoc private static final Logger log = LoggerFactory.getLogger(AbstractValidatingSessionManager.class); /** * 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; /** * Scheduler used to validate sessions on a regular basis. */ protected SessionValidationScheduler sessionValidationScheduler; protected long sessionValidationInterval; public AbstractValidatingSessionManager() { this.sessionValidationSchedulerEnabled = true; this.sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL; } public boolean isSessionValidationSchedulerEnabled() { return sessionValidationSchedulerEnabled; } @SuppressWarnings({"UnusedDeclaration"}) public void setSessionValidationSchedulerEnabled(boolean sessionValidationSchedulerEnabled) { this.sessionValidationSchedulerEnabled = sessionValidationSchedulerEnabled; } public void setSessionValidationScheduler(SessionValidationScheduler sessionValidationScheduler) { this.sessionValidationScheduler = sessionValidationScheduler; } public SessionValidationScheduler getSessionValidationScheduler() { return sessionValidationScheduler; } private void enableSessionValidationIfNecessary() { SessionValidationScheduler scheduler = getSessionValidationScheduler(); if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) { enableSessionValidation(); } } /** * 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; } @Override protected final Session doGetSession(final SessionKey key) throws InvalidSessionException { enableSessionValidationIfNecessary(); log.trace("Attempting to retrieve session with key {}", key); Session s = retrieveSession(key); if (s != null) { validate(s, key); } return s; } /** * Looks up a session from the underlying data store based on the specified session key. * * @param key the session key to use to look up the target session. * @return the session identified by {@code sessionId}. * @throws UnknownSessionException if there is no session identified by {@code sessionId}. */ protected abstract Session retrieveSession(SessionKey key) throws UnknownSessionException; protected Session createSession(SessionContext context) throws AuthorizationException { enableSessionValidationIfNecessary(); return doCreateSession(context); } protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException; protected void validate(Session session, SessionKey key) throws InvalidSessionException { try { doValidate(session); } catch (ExpiredSessionException ese) { onExpiration(session, ese, key); throw ese; } catch (InvalidSessionException ise) { onInvalidation(session, ise, key); throw ise; } } protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) { log.trace("Session with id [{}] has expired.", s.getId()); try { onExpiration(s); notifyExpiration(s); } finally { afterExpired(s); } } protected void onExpiration(Session session) { onChange(session); } protected void afterExpired(Session session) { } protected void onInvalidation(Session s, InvalidSessionException ise, SessionKey key) { if (ise instanceof ExpiredSessionException) { onExpiration(s, (ExpiredSessionException) ise, key); return; } log.trace("Session with id [{}] is invalid.", s.getId()); try { onStop(s); notifyStop(s); } finally { afterStopped(s); } } protected void doValidate(Session session) throws InvalidSessionException { if (session instanceof ValidatingSession) { ((ValidatingSession) session).validate(); } else { String msg = "The " + getClass().getName() + " implementation only supports validating " + "Session implementations of the " + ValidatingSession.class.getName() + " interface. " + "Please either implement this interface in your session implementation or override the " + AbstractValidatingSessionManager.class.getName() + ".doValidate(Session) method to perform validation."; throw new IllegalStateException(msg); } } /** * Subclass template hook in case per-session timeout is not based on * {@link com.hwlcn.security.session.Session#getTimeout()}. * <p/> * <p>This implementation merely returns {@link com.hwlcn.security.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() { ExecutorServiceSessionValidationScheduler scheduler; if (log.isDebugEnabled()) { log.debug("No sessionValidationScheduler set. Attempting to create default instance."); } scheduler = new ExecutorServiceSessionValidationScheduler(this); scheduler.setInterval(getSessionValidationInterval()); if (log.isTraceEnabled()) { log.trace("Created default SessionValidationScheduler instance of type [" + scheduler.getClass().getName() + "]."); } return scheduler; } protected void enableSessionValidation() { SessionValidationScheduler scheduler = getSessionValidationScheduler(); if (scheduler == null) { scheduler = createSessionValidationScheduler(); setSessionValidationScheduler(scheduler); } if (log.isInfoEnabled()) { log.info("Enabling session validation scheduler..."); } scheduler.enableSessionValidation(); afterSessionValidationEnabled(); } protected void afterSessionValidationEnabled() { } protected void disableSessionValidation() { beforeSessionValidationDisabled(); SessionValidationScheduler scheduler = getSessionValidationScheduler(); if (scheduler != null) { try { scheduler.disableSessionValidation(); if (log.isInfoEnabled()) { log.info("Disabled session validation scheduler."); } } catch (Exception e) { if (log.isDebugEnabled()) { String msg = "Unable to disable SessionValidationScheduler. Ignoring (shutting down)..."; log.debug(msg, e); } } LifecycleUtils.destroy(scheduler); setSessionValidationScheduler(null); } } protected void beforeSessionValidationDisabled() { } public void destroy() { disableSessionValidation(); } /** * @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 { //simulate a lookup key to satisfy the method signature. //this could probably stand to be cleaned up in future versions: SessionKey key = new DefaultSessionKey(s.getId()); validate(s, key); } 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(); }