blob: c7b4f72e5bbf579c779c79ce599a84cefe60969d [file] [log] [blame]
/*
* Copyright 2008 Les Hazlewood
*
* 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.apache.shiro.session.mgt;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.session.*;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
/**
* Abstract implementation supporting the {@link NativeSessionManager NativeSessionManager} interface, supporting
* {@link SessionListener SessionListener}s and application of the
* {@link #getGlobalSessionTimeout() globalSessionTimeout}.
*
* @since 1.0
*/
public abstract class AbstractNativeSessionManager extends AbstractSessionManager implements NativeSessionManager {
private static final Logger log = LoggerFactory.getLogger(AbstractSessionManager.class);
private Collection<SessionListener> listeners;
public AbstractNativeSessionManager() {
this.listeners = new ArrayList<SessionListener>();
}
public void setSessionListeners(Collection<SessionListener> listeners) {
this.listeners = listeners != null ? listeners : new ArrayList<SessionListener>();
}
@SuppressWarnings({"UnusedDeclaration"})
public Collection<SessionListener> getSessionListeners() {
return this.listeners;
}
public Session start(SessionContext context) {
Session session = createSession(context);
session.setTimeout(getGlobalSessionTimeout()); //todo this configuration in Session creation
if (isUpdateImmediate(context)) {
onChange(session);
}
onStart(session, context);
notifyStart(session);
//Don't expose the EIS-tier Session object to the client-tier:
return createExposedSession(session, context);
}
/**
* Creates a new {@code Session Session} instance based on the specified (possibly {@code null})
* initialization data. Implementing classes must manage the persistent state of the returned session such that it
* could later be acquired via the {@link #getSession(SessionKey)} method.
*
* @param context the initialization data that can be used by the implementation or underlying
* {@link SessionFactory} when instantiating the internal {@code Session} instance.
* @return the new {@code Session} instance.
* @throws org.apache.shiro.authz.HostUnauthorizedException
* if the system access control policy restricts access based
* on client location/IP and the specified hostAddress hasn't been enabled.
* @throws AuthorizationException if the system access control policy does not allow the currently executing
* caller to start sessions.
*/
protected abstract Session createSession(SessionContext context) throws AuthorizationException;
/**
* Template method that allows subclasses to react to a new session being created.
* <p/>
* This method is invoked <em>before</em> any session listeners are notified.
*
* @param session the session that was just {@link #createSession created}.
* @param context the {@link SessionContext SessionContext} that was used to start the session.
*/
protected void onStart(Session session, SessionContext context) {
}
public Session getSession(SessionKey key) throws SessionException {
Session session = lookupSession(key);
return session != null ? createExposedSession(session, key) : null;
}
private Session lookupSession(SessionKey key) throws SessionException {
if (key == null) {
throw new NullPointerException("SessionKey argument cannot be null.");
}
return doGetSession(key);
}
private Session lookupRequiredSession(SessionKey key) throws SessionException {
Session session = lookupSession(key);
if (session == null) {
String msg = "Unable to locate required Session instance based on SessionKey [" + key + "].";
throw new UnknownSessionException(msg);
}
return session;
}
protected abstract Session doGetSession(SessionKey key) throws InvalidSessionException;
protected Session createExposedSession(Session session, SessionContext context) {
SessionKey sessionKey = createSessionKey(session, context);
return new DelegatingSession(this, sessionKey);
}
//since 1.3
protected SessionKey createSessionKey(Session session, SessionContext context) {
SessionKey key = doCreateSessionKey(session, context);
applyUpdateDeferred(key, context);
return key;
}
//since 1.3
protected SessionKey createSessionKey(Session session, SessionKey oldKey) {
SessionKey newKey = doCreateSessionKey(session, oldKey);
applyUpdateDeferred(newKey, oldKey);
return newKey;
}
//since 1.3
protected SessionKey doCreateSessionKey(Session session, SessionContext context) {
return new DefaultSessionKey(session.getId());
}
//since 1.3
protected SessionKey doCreateSessionKey(Session session, SessionKey sessionKey) {
return new DefaultSessionKey(session.getId());
}
protected Session createExposedSession(Session session, SessionKey key) {
SessionKey sessionKey = createSessionKey(session, key);
return new DelegatingSession(this, sessionKey);
}
/**
* Returns the session instance to use to pass to registered {@code SessionListener}s for notification
* that the session has been invalidated (stopped or expired).
* <p/>
* The default implementation returns an {@link ImmutableProxiedSession ImmutableProxiedSession} instance to ensure
* that the specified {@code session} argument is not modified by any listeners.
*
* @param session the {@code Session} object being invalidated.
* @return the {@code Session} instance to use to pass to registered {@code SessionListener}s for notification.
*/
protected Session beforeInvalidNotification(Session session) {
return new ImmutableProxiedSession(session);
}
/**
* Notifies any interested {@link SessionListener}s that a Session has started. This method is invoked
* <em>after</em> the {@link #onStart onStart} method is called.
*
* @param session the session that has just started that will be delivered to any
* {@link #setSessionListeners(java.util.Collection) registered} session listeners.
* @see SessionListener#onStart(org.apache.shiro.session.Session)
*/
protected void notifyStart(Session session) {
for (SessionListener listener : this.listeners) {
listener.onStart(session);
}
}
protected void notifyStop(Session session) {
Session forNotification = beforeInvalidNotification(session);
for (SessionListener listener : this.listeners) {
listener.onStop(forNotification);
}
}
protected void notifyExpiration(Session session) {
Session forNotification = beforeInvalidNotification(session);
for (SessionListener listener : this.listeners) {
listener.onExpiration(forNotification);
}
}
public Date getStartTimestamp(SessionKey key) {
return lookupRequiredSession(key).getStartTimestamp();
}
public Date getLastAccessTime(SessionKey key) {
return lookupRequiredSession(key).getLastAccessTime();
}
public long getTimeout(SessionKey key) throws InvalidSessionException {
return lookupRequiredSession(key).getTimeout();
}
public void setTimeout(SessionKey key, long maxIdleTimeInMillis) throws InvalidSessionException {
Session s = lookupRequiredSession(key);
s.setTimeout(maxIdleTimeInMillis);
if (isUpdateImmediate(key)) {
onChange(s);
}
}
public void touch(SessionKey key) throws InvalidSessionException {
Session s = lookupRequiredSession(key);
s.touch();
if (isUpdateImmediate(key)) {
onChange(s);
}
}
public String getHost(SessionKey key) {
return lookupRequiredSession(key).getHost();
}
public Collection<Object> getAttributeKeys(SessionKey key) {
Collection<Object> c = lookupRequiredSession(key).getAttributeKeys();
if (!CollectionUtils.isEmpty(c)) {
return Collections.unmodifiableCollection(c);
}
return Collections.emptySet();
}
public Object getAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException {
return lookupRequiredSession(sessionKey).getAttribute(attributeKey);
}
public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException {
if (value == null) {
removeAttribute(sessionKey, attributeKey);
} else {
Session s = lookupRequiredSession(sessionKey);
s.setAttribute(attributeKey, value);
if (isUpdateImmediate(sessionKey)) {
onChange(s);
}
}
}
//since 1.3
protected final boolean isUpdateDeferred(Object object) {
return object instanceof UpdateDeferrable && ((UpdateDeferrable)object).isUpdateDeferred();
}
//since 1.3
protected final void applyUpdateDeferred(Object target, Object source) {
if (target instanceof UpdateDeferrable) {
((UpdateDeferrable)target).setUpdateDeferred(isUpdateDeferred(source));
}
}
//since 1.3
protected final boolean isUpdateImmediate(Object object) {
return !isUpdateDeferred(object);
}
public Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException {
Session s = lookupRequiredSession(sessionKey);
Object removed = s.removeAttribute(attributeKey);
if (removed != null && isUpdateImmediate(sessionKey)) {
onChange(s);
}
return removed;
}
public boolean isValid(SessionKey key) {
try {
checkValid(key);
return true;
} catch (InvalidSessionException e) {
return false;
}
}
public void stop(SessionKey key) throws InvalidSessionException {
Session session = lookupRequiredSession(key);
if (log.isDebugEnabled()) {
log.debug("Stopping session with id [" + session.getId() + "]");
}
session.stop();
onStop(session, key);
notifyStop(session);
afterStopped(session);
}
protected void onStop(Session session, SessionKey key) {
onStop(session);
}
protected void onStop(Session session) {
onChange(session);
}
protected void afterStopped(Session session) {
}
public void checkValid(SessionKey key) throws InvalidSessionException {
//just try to acquire it. If there is a problem, an exception will be thrown:
lookupRequiredSession(key);
}
protected void onChange(Session s) {
}
}