All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.catalina.session.StandardSession Maven / Gradle / Ivy

There is a newer version: 11.0.0-M26
Show newest version
/*
 * 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 org.apache.catalina.session;

import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.io.WriteAbortedException;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionIdListener;
import javax.servlet.http.HttpSessionListener;

import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.SessionEvent;
import org.apache.catalina.SessionListener;
import org.apache.catalina.TomcatPrincipal;
import org.apache.catalina.authenticator.SavedRequest;
import org.apache.catalina.security.SecurityUtil;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.res.StringManager;

/**
 * Standard implementation of the Session interface. This object is serializable, so that it can be stored in
 * persistent storage or transferred to a different JVM for distributable session support.
 * 

* IMPLEMENTATION NOTE: An instance of this class represents both the internal (Session) and application level * (HttpSession) view of the session. However, because the class itself is not declared public, Java logic outside of * the org.apache.catalina.session package cannot cast an HttpSession view of this instance back to a * Session view. *

* IMPLEMENTATION NOTE: If you add fields to this class, you must make sure that you carry them over in the * read/writeObject methods so that this class is properly serialized. * * @author Craig R. McClanahan * @author Sean Legassick * @author Jon S. Stevens */ public class StandardSession implements HttpSession, Session, Serializable { private static final long serialVersionUID = 1L; protected static final boolean STRICT_SERVLET_COMPLIANCE; protected static final boolean ACTIVITY_CHECK; protected static final boolean LAST_ACCESS_AT_START; static { STRICT_SERVLET_COMPLIANCE = Globals.STRICT_SERVLET_COMPLIANCE; String activityCheck = System.getProperty("org.apache.catalina.session.StandardSession.ACTIVITY_CHECK"); if (activityCheck == null) { ACTIVITY_CHECK = STRICT_SERVLET_COMPLIANCE; } else { ACTIVITY_CHECK = Boolean.parseBoolean(activityCheck); } String lastAccessAtStart = System.getProperty("org.apache.catalina.session.StandardSession.LAST_ACCESS_AT_START"); if (lastAccessAtStart == null) { LAST_ACCESS_AT_START = STRICT_SERVLET_COMPLIANCE; } else { LAST_ACCESS_AT_START = Boolean.parseBoolean(lastAccessAtStart); } } // ----------------------------------------------------------- Constructors /** * Construct a new Session associated with the specified Manager. * * @param manager The manager with which this Session is associated */ public StandardSession(Manager manager) { super(); this.manager = manager; // Initialize access count if (ACTIVITY_CHECK) { accessCount = new AtomicInteger(); } } // ----------------------------------------------------- Instance Variables /** * Type array. */ protected static final String EMPTY_ARRAY[] = new String[0]; /** * The collection of user data attributes associated with this Session. */ protected ConcurrentMap attributes = new ConcurrentHashMap<>(); /** * The authentication type used to authenticate our cached Principal, if any. NOTE: This value is not included in * the serialized version of this object. */ protected transient String authType = null; /** * The time this session was created, in milliseconds since midnight, January 1, 1970 GMT. */ protected long creationTime = 0L; /** * We are currently processing a session expiration, so bypass certain IllegalStateException tests. NOTE: This value * is not included in the serialized version of this object. */ protected transient volatile boolean expiring = false; /** * The facade associated with this session. NOTE: This value is not included in the serialized version of this * object. */ protected transient StandardSessionFacade facade = null; /** * The session identifier of this Session. */ protected String id = null; /** * The last accessed time for this Session. */ protected volatile long lastAccessedTime = creationTime; /** * The session event listeners for this Session. */ protected transient ArrayList listeners = new ArrayList<>(); /** * The Manager with which this Session is associated. */ protected transient Manager manager = null; /** * The maximum time interval, in seconds, between client requests before the servlet container may invalidate this * session. A negative time indicates that the session should never time out. */ protected volatile int maxInactiveInterval = -1; /** * Flag indicating whether this session is new or not. */ protected volatile boolean isNew = false; /** * Flag indicating whether this session is valid or not. */ protected volatile boolean isValid = false; /** * Internal notes associated with this session by Catalina components and event listeners. IMPLEMENTATION * NOTE: This object is not saved and restored across session serializations! */ protected transient Map notes = new ConcurrentHashMap<>(); /** * The authenticated Principal associated with this session, if any. IMPLEMENTATION NOTE: This object is * not saved and restored across session serializations! */ protected transient Principal principal = null; /** * The string manager for this package. */ protected static final StringManager sm = StringManager.getManager(StandardSession.class); /** * The HTTP session context associated with this session. */ @Deprecated protected static volatile javax.servlet.http.HttpSessionContext sessionContext = null; /** * The property change support for this component. NOTE: This value is not included in the serialized version of * this object. */ protected final transient PropertyChangeSupport support = new PropertyChangeSupport(this); /** * The current accessed time for this session. */ protected volatile long thisAccessedTime = creationTime; /** * The access count for this session. */ protected transient AtomicInteger accessCount = null; // ----------------------------------------------------- Session Properties /** * Return the authentication type used to authenticate our cached Principal, if any. */ @Override public String getAuthType() { return this.authType; } /** * Set the authentication type used to authenticate our cached Principal, if any. * * @param authType The new cached authentication type */ @Override public void setAuthType(String authType) { String oldAuthType = this.authType; this.authType = authType; support.firePropertyChange("authType", oldAuthType, this.authType); } /** * Set the creation time for this session. This method is called by the Manager when an existing Session instance is * reused. * * @param time The new creation time */ @Override public void setCreationTime(long time) { this.creationTime = time; this.lastAccessedTime = time; this.thisAccessedTime = time; } /** * Return the session identifier for this session. */ @Override public String getId() { return this.id; } /** * Return the session identifier for this session. */ @Override public String getIdInternal() { return this.id; } /** * Set the session identifier for this session. * * @param id The new session identifier */ @Override public void setId(String id) { setId(id, true); } @Override public void setId(String id, boolean notify) { if ((this.id != null) && (manager != null)) { manager.remove(this); } this.id = id; if (manager != null) { manager.add(this); } if (notify) { tellNew(); } } /** * Inform the listeners about the new session. */ public void tellNew() { // Notify interested session event listeners fireSessionEvent(SESSION_CREATED_EVENT, null); // Notify interested application event listeners Context context = manager.getContext(); Object listeners[] = context.getApplicationLifecycleListeners(); if (listeners != null && listeners.length > 0) { HttpSessionEvent event = new HttpSessionEvent(getSession()); for (Object o : listeners) { if (!(o instanceof HttpSessionListener)) { continue; } HttpSessionListener listener = (HttpSessionListener) o; try { context.fireContainerEvent("beforeSessionCreated", listener); listener.sessionCreated(event); context.fireContainerEvent("afterSessionCreated", listener); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); try { context.fireContainerEvent("afterSessionCreated", listener); } catch (Exception e) { // Ignore } manager.getContext().getLogger().error(sm.getString("standardSession.sessionEvent"), t); } } } } /** * Inform the listeners about the change session ID. * * @param newId new session ID * @param oldId old session ID * @param notifySessionListeners Should any associated sessionListeners be notified that session ID has been * changed? * @param notifyContainerListeners Should any associated ContainerListeners be notified that session ID has been * changed? */ @Override public void tellChangedSessionId(String newId, String oldId, boolean notifySessionListeners, boolean notifyContainerListeners) { Context context = manager.getContext(); // notify ContainerListeners if (notifyContainerListeners) { context.fireContainerEvent(Context.CHANGE_SESSION_ID_EVENT, new String[] { oldId, newId }); } // notify HttpSessionIdListener if (notifySessionListeners) { Object listeners[] = context.getApplicationEventListeners(); if (listeners != null && listeners.length > 0) { HttpSessionEvent event = new HttpSessionEvent(getSession()); for (Object listener : listeners) { if (!(listener instanceof HttpSessionIdListener)) { continue; } HttpSessionIdListener idListener = (HttpSessionIdListener) listener; try { idListener.sessionIdChanged(event, oldId); } catch (Throwable t) { manager.getContext().getLogger().error(sm.getString("standardSession.sessionEvent"), t); } } } } } /** * Return the last time the client sent a request associated with this session, as the number of milliseconds since * midnight, January 1, 1970 GMT. Actions that your application takes, such as getting or setting a value associated * with the session, do not affect the access time. This one gets updated whenever a request starts. */ @Override public long getThisAccessedTime() { if (!isValidInternal()) { throw new IllegalStateException(sm.getString("standardSession.getThisAccessedTime.ise")); } return this.thisAccessedTime; } /** * Return the last client access time without invalidation check * * @see #getThisAccessedTime() */ @Override public long getThisAccessedTimeInternal() { return this.thisAccessedTime; } /** * Return the last time the client sent a request associated with this session, as the number of milliseconds since * midnight, January 1, 1970 GMT. Actions that your application takes, such as getting or setting a value associated * with the session, do not affect the access time. This one gets updated whenever a request finishes. */ @Override public long getLastAccessedTime() { if (!isValidInternal()) { throw new IllegalStateException(sm.getString("standardSession.getLastAccessedTime.ise")); } return this.lastAccessedTime; } /** * Return the last client access time without invalidation check * * @see #getLastAccessedTime() */ @Override public long getLastAccessedTimeInternal() { return this.lastAccessedTime; } /** * Return the idle time (in milliseconds) from last client access time. */ @Override public long getIdleTime() { if (!isValidInternal()) { throw new IllegalStateException(sm.getString("standardSession.getIdleTime.ise")); } return getIdleTimeInternal(); } /** * Return the idle time from last client access time without invalidation check * * @see #getIdleTime() */ @Override public long getIdleTimeInternal() { long timeNow = System.currentTimeMillis(); long timeIdle; if (LAST_ACCESS_AT_START) { timeIdle = timeNow - lastAccessedTime; } else { timeIdle = timeNow - thisAccessedTime; } return timeIdle; } /** * Return the Manager within which this Session is valid. */ @Override public Manager getManager() { return this.manager; } /** * Set the Manager within which this Session is valid. * * @param manager The new Manager */ @Override public void setManager(Manager manager) { this.manager = manager; } /** * Return the maximum time interval, in seconds, between client requests before the servlet container will * invalidate the session. A negative time indicates that the session should never time out. */ @Override public int getMaxInactiveInterval() { return this.maxInactiveInterval; } /** * Set the maximum time interval, in seconds, between client requests before the servlet container will invalidate * the session. A zero or negative time indicates that the session should never time out. * * @param interval The new maximum interval */ @Override public void setMaxInactiveInterval(int interval) { this.maxInactiveInterval = interval; } /** * Set the isNew flag for this session. * * @param isNew The new value for the isNew flag */ @Override public void setNew(boolean isNew) { this.isNew = isNew; } /** * Return the authenticated Principal that is associated with this Session. This provides an * Authenticator with a means to cache a previously authenticated Principal, and avoid potentially * expensive Realm.authenticate() calls on every request. If there is no current associated Principal, * return null. */ @Override public Principal getPrincipal() { return this.principal; } /** * Set the authenticated Principal that is associated with this Session. This provides an Authenticator * with a means to cache a previously authenticated Principal, and avoid potentially expensive * Realm.authenticate() calls on every request. * * @param principal The new Principal, or null if none */ @Override public void setPrincipal(Principal principal) { Principal oldPrincipal = this.principal; this.principal = principal; support.firePropertyChange("principal", oldPrincipal, this.principal); } /** * Return the HttpSession for which this object is the facade. */ @Override public HttpSession getSession() { if (facade == null) { if (SecurityUtil.isPackageProtectionEnabled()) { facade = AccessController.doPrivileged(new PrivilegedNewSessionFacade(this)); } else { facade = new StandardSessionFacade(this); } } return facade; } /** * Return the isValid flag for this session. */ @Override public boolean isValid() { if (!this.isValid) { return false; } if (this.expiring) { return true; } if (ACTIVITY_CHECK && accessCount.get() > 0) { return true; } if (maxInactiveInterval > 0) { int timeIdle = (int) (getIdleTimeInternal() / 1000L); if (timeIdle >= maxInactiveInterval) { expire(true); } } return this.isValid; } /** * Set the isValid flag for this session. * * @param isValid The new value for the isValid flag */ @Override public void setValid(boolean isValid) { this.isValid = isValid; } // ------------------------------------------------- Session Public Methods /** * Update the accessed time information for this session. This method should be called by the context when a request * comes in for a particular session, even if the application does not reference it. */ @Override public void access() { this.thisAccessedTime = System.currentTimeMillis(); if (ACTIVITY_CHECK) { accessCount.incrementAndGet(); } } /** * End the access. */ @Override public void endAccess() { isNew = false; /* * The servlet spec mandates to ignore request handling time in lastAccessedTime. */ if (LAST_ACCESS_AT_START) { this.lastAccessedTime = this.thisAccessedTime; this.thisAccessedTime = System.currentTimeMillis(); } else { this.thisAccessedTime = System.currentTimeMillis(); this.lastAccessedTime = this.thisAccessedTime; } if (ACTIVITY_CHECK) { accessCount.decrementAndGet(); } } /** * Add a session event listener to this component. */ @Override public void addSessionListener(SessionListener listener) { listeners.add(listener); } /** * Perform the internal processing required to invalidate this session, without triggering an exception if the * session has already expired. */ @Override public void expire() { expire(true); } /** * Perform the internal processing required to invalidate this session, without triggering an exception if the * session has already expired. * * @param notify Should we notify listeners about the demise of this session? */ public void expire(boolean notify) { // Check to see if session has already been invalidated. // Do not check expiring at this point as expire should not return until // isValid is false if (!isValid) { return; } synchronized (this) { // Check again, now we are inside the sync so this code only runs once // Double check locking - isValid needs to be volatile // The check of expiring is to ensure that an infinite loop is not // entered as per bug 56339 if (expiring || !isValid) { return; } if (manager == null) { return; } // Mark this session as "being expired" expiring = true; // Notify interested application event listeners // FIXME - Assumes we call listeners in reverse order Context context = manager.getContext(); // The call to expire() may not have been triggered by the webapp. // Make sure the webapp's class loader is set when calling the // listeners if (notify) { ClassLoader oldContextClassLoader = null; try { oldContextClassLoader = context.bind(Globals.IS_SECURITY_ENABLED, null); Object listeners[] = context.getApplicationLifecycleListeners(); if (listeners != null && listeners.length > 0) { HttpSessionEvent event = new HttpSessionEvent(getSession()); for (int i = 0; i < listeners.length; i++) { int j = (listeners.length - 1) - i; if (!(listeners[j] instanceof HttpSessionListener)) { continue; } HttpSessionListener listener = (HttpSessionListener) listeners[j]; try { context.fireContainerEvent("beforeSessionDestroyed", listener); listener.sessionDestroyed(event); context.fireContainerEvent("afterSessionDestroyed", listener); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); try { context.fireContainerEvent("afterSessionDestroyed", listener); } catch (Exception e) { // Ignore } manager.getContext().getLogger().error(sm.getString("standardSession.sessionEvent"), t); } } } } finally { context.unbind(Globals.IS_SECURITY_ENABLED, oldContextClassLoader); } } if (ACTIVITY_CHECK) { accessCount.set(0); } // Remove this session from our manager's active sessions manager.remove(this, true); // Notify interested session event listeners if (notify) { fireSessionEvent(SESSION_DESTROYED_EVENT, null); } // Call the logout method if (principal instanceof TomcatPrincipal) { TomcatPrincipal gp = (TomcatPrincipal) principal; try { gp.logout(); } catch (Exception e) { manager.getContext().getLogger().error(sm.getString("standardSession.logoutfail"), e); } } // We have completed expire of this session setValid(false); expiring = false; // Unbind any objects associated with this session String keys[] = keys(); ClassLoader oldContextClassLoader = null; try { oldContextClassLoader = context.bind(Globals.IS_SECURITY_ENABLED, null); for (String key : keys) { removeAttributeInternal(key, notify); } } finally { context.unbind(Globals.IS_SECURITY_ENABLED, oldContextClassLoader); } } } /** * Perform the internal processing required to passivate this session. */ public void passivate() { // Notify interested session event listeners fireSessionEvent(SESSION_PASSIVATED_EVENT, null); // Notify ActivationListeners HttpSessionEvent event = null; String keys[] = keys(); for (String key : keys) { Object attribute = attributes.get(key); if (attribute instanceof HttpSessionActivationListener) { if (event == null) { event = new HttpSessionEvent(getSession()); } try { ((HttpSessionActivationListener) attribute).sessionWillPassivate(event); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); manager.getContext().getLogger().error(sm.getString("standardSession.attributeEvent"), t); } } } } /** * Perform internal processing required to activate this session. */ public void activate() { // Initialize access count if (ACTIVITY_CHECK) { accessCount = new AtomicInteger(); } // Notify interested session event listeners fireSessionEvent(SESSION_ACTIVATED_EVENT, null); // Notify ActivationListeners HttpSessionEvent event = null; String keys[] = keys(); for (String key : keys) { Object attribute = attributes.get(key); if (attribute instanceof HttpSessionActivationListener) { if (event == null) { event = new HttpSessionEvent(getSession()); } try { ((HttpSessionActivationListener) attribute).sessionDidActivate(event); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); manager.getContext().getLogger().error(sm.getString("standardSession.attributeEvent"), t); } } } } /** * Return the object bound with the specified name to the internal notes for this session, or null if * no such binding exists. * * @param name Name of the note to be returned */ @Override public Object getNote(String name) { return notes.get(name); } /** * Return an Iterator containing the String names of all notes bindings that exist for this session. */ @Override public Iterator getNoteNames() { return notes.keySet().iterator(); } /** * Release all object references, and initialize instance variables, in preparation for reuse of this object. */ @Override public void recycle() { // Reset the instance variables associated with this Session attributes.clear(); setAuthType(null); creationTime = 0L; expiring = false; id = null; lastAccessedTime = 0L; maxInactiveInterval = -1; notes.clear(); setPrincipal(null); isNew = false; isValid = false; manager = null; } /** * Remove any object bound to the specified name in the internal notes for this session. * * @param name Name of the note to be removed */ @Override public void removeNote(String name) { notes.remove(name); } /** * Remove a session event listener from this component. */ @Override public void removeSessionListener(SessionListener listener) { listeners.remove(listener); } /** * Bind an object to a specified name in the internal notes associated with this session, replacing any existing * binding for this name. * * @param name Name to which the object should be bound * @param value Object to be bound to the specified name */ @Override public void setNote(String name, Object value) { notes.put(name, value); } /** * Return a string representation of this object. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("StandardSession["); sb.append(id); sb.append(']'); return sb.toString(); } // ------------------------------------------------ Session Package Methods /** * Read a serialized version of the contents of this session object from the specified object input stream, without * requiring that the StandardSession itself have been serialized. * * @param stream The object input stream to read from * * @exception ClassNotFoundException if an unknown class is specified * @exception IOException if an input/output error occurs */ public void readObjectData(ObjectInputStream stream) throws ClassNotFoundException, IOException { doReadObject(stream); } /** * Write a serialized version of the contents of this session object to the specified object output stream, without * requiring that the StandardSession itself have been serialized. * * @param stream The object output stream to write to * * @exception IOException if an input/output error occurs */ public void writeObjectData(ObjectOutputStream stream) throws IOException { doWriteObject(stream); } // ------------------------------------------------- HttpSession Properties /** * Return the time when this session was created, in milliseconds since midnight, January 1, 1970 GMT. * * @exception IllegalStateException if this method is called on an invalidated session */ @Override public long getCreationTime() { if (!isValidInternal()) { throw new IllegalStateException(sm.getString("standardSession.getCreationTime.ise")); } return this.creationTime; } /** * Return the time when this session was created, in milliseconds since midnight, January 1, 1970 GMT, bypassing the * session validation checks. */ @Override public long getCreationTimeInternal() { return this.creationTime; } /** * Return the ServletContext to which this session belongs. */ @Override public ServletContext getServletContext() { if (manager == null) { return null; } Context context = manager.getContext(); return context.getServletContext(); } /** * Return the session context with which this session is associated. * * @deprecated As of Version 2.1, this method is deprecated and has no replacement. It will be removed in a future * version of the Java Servlet API. */ @Override @Deprecated public javax.servlet.http.HttpSessionContext getSessionContext() { if (sessionContext == null) { sessionContext = new StandardSessionContext(); } return sessionContext; } // ----------------------------------------------HttpSession Public Methods /** * Return the object bound with the specified name in this session, or null if no object is bound with * that name. * * @param name Name of the attribute to be returned * * @exception IllegalStateException if this method is called on an invalidated session */ @Override public Object getAttribute(String name) { if (!isValidInternal()) { throw new IllegalStateException(sm.getString("standardSession.getAttribute.ise")); } if (name == null) { return null; } return attributes.get(name); } /** * Return an Enumeration of String objects containing the names of the objects bound to * this session. * * @exception IllegalStateException if this method is called on an invalidated session */ @Override public Enumeration getAttributeNames() { if (!isValidInternal()) { throw new IllegalStateException(sm.getString("standardSession.getAttributeNames.ise")); } Set names = new HashSet<>(attributes.keySet()); return Collections.enumeration(names); } /** * Return the object bound with the specified name in this session, or null if no object is bound with * that name. * * @param name Name of the value to be returned * * @exception IllegalStateException if this method is called on an invalidated session * * @deprecated As of Version 2.2, this method is replaced by getAttribute() */ @Override @Deprecated public Object getValue(String name) { return getAttribute(name); } /** * Return the set of names of objects bound to this session. If there are no such objects, a zero-length array is * returned. * * @exception IllegalStateException if this method is called on an invalidated session * * @deprecated As of Version 2.2, this method is replaced by getAttributeNames() */ @Override @Deprecated public String[] getValueNames() { if (!isValidInternal()) { throw new IllegalStateException(sm.getString("standardSession.getValueNames.ise")); } return keys(); } /** * Invalidates this session and unbinds any objects bound to it. * * @exception IllegalStateException if this method is called on an invalidated session */ @Override public void invalidate() { if (!isValidInternal()) { throw new IllegalStateException(sm.getString("standardSession.invalidate.ise")); } // Cause this session to expire expire(); } /** * Return true if the client does not yet know about the session, or if the client chooses not to join * the session. For example, if the server used only cookie-based sessions, and the client has disabled the use of * cookies, then a session would be new on each request. * * @exception IllegalStateException if this method is called on an invalidated session */ @Override public boolean isNew() { if (!isValidInternal()) { throw new IllegalStateException(sm.getString("standardSession.isNew.ise")); } return this.isNew; } /** * Bind an object to this session, using the specified name. If an object of the same name is already bound to this * session, the object is replaced. *

* After this method executes, and if the object implements HttpSessionBindingListener, the container * calls valueBound() on the object. * * @param name Name to which the object is bound, cannot be null * @param value Object to be bound, cannot be null * * @exception IllegalStateException if this method is called on an invalidated session * * @deprecated As of Version 2.2, this method is replaced by setAttribute() */ @Override @Deprecated public void putValue(String name, Object value) { setAttribute(name, value); } /** * Remove the object bound with the specified name from this session. If the session does not have an object bound * with this name, this method does nothing. *

* After this method executes, and if the object implements HttpSessionBindingListener, the container * calls valueUnbound() on the object. * * @param name Name of the object to remove from this session. * * @exception IllegalStateException if this method is called on an invalidated session */ @Override public void removeAttribute(String name) { removeAttribute(name, true); } /** * Remove the object bound with the specified name from this session. If the session does not have an object bound * with this name, this method does nothing. *

* After this method executes, and if the object implements HttpSessionBindingListener, the container * calls valueUnbound() on the object. * * @param name Name of the object to remove from this session. * @param notify Should we notify interested listeners that this attribute is being removed? * * @exception IllegalStateException if this method is called on an invalidated session */ public void removeAttribute(String name, boolean notify) { // Validate our current state if (!isValidInternal()) { throw new IllegalStateException(sm.getString("standardSession.removeAttribute.ise")); } removeAttributeInternal(name, notify); } /** * Remove the object bound with the specified name from this session. If the session does not have an object bound * with this name, this method does nothing. *

* After this method executes, and if the object implements HttpSessionBindingListener, the container * calls valueUnbound() on the object. * * @param name Name of the object to remove from this session. * * @exception IllegalStateException if this method is called on an invalidated session * * @deprecated As of Version 2.2, this method is replaced by removeAttribute() */ @Override @Deprecated public void removeValue(String name) { removeAttribute(name); } /** * Bind an object to this session, using the specified name. If an object of the same name is already bound to this * session, the object is replaced. *

* After this method executes, and if the object implements HttpSessionBindingListener, the container * calls valueBound() on the object. * * @param name Name to which the object is bound, cannot be null * @param value Object to be bound, cannot be null * * @exception IllegalArgumentException if an attempt is made to add a non-serializable object in an environment * marked distributable. * @exception IllegalStateException if this method is called on an invalidated session */ @Override public void setAttribute(String name, Object value) { setAttribute(name, value, true); } /** * Bind an object to this session, using the specified name. If an object of the same name is already bound to this * session, the object is replaced. *

* After this method executes, and if the object implements HttpSessionBindingListener, the container * calls valueBound() on the object. * * @param name Name to which the object is bound, cannot be null * @param value Object to be bound, cannot be null * @param notify whether to notify session listeners * * @exception IllegalArgumentException if an attempt is made to add a non-serializable object in an environment * marked distributable. * @exception IllegalStateException if this method is called on an invalidated session */ public void setAttribute(String name, Object value, boolean notify) { // Name cannot be null if (name == null) { throw new IllegalArgumentException(sm.getString("standardSession.setAttribute.namenull")); } // Null value is the same as removeAttribute() if (value == null) { removeAttribute(name); return; } // Validate our current state if (!isValidInternal()) { throw new IllegalStateException(sm.getString("standardSession.setAttribute.ise", getIdInternal())); } Context context = manager.getContext(); if (context.getDistributable() && !isAttributeDistributable(name, value) && !exclude(name, value)) { throw new IllegalArgumentException(sm.getString("standardSession.setAttribute.iae", name)); } // Construct an event with the new value HttpSessionBindingEvent event = null; // Call the valueBound() method if necessary if (notify && value instanceof HttpSessionBindingListener) { // Don't call any notification if replacing with the same value // unless configured to do so Object oldValue = attributes.get(name); if (value != oldValue || manager.getNotifyBindingListenerOnUnchangedValue()) { event = new HttpSessionBindingEvent(getSession(), name, value); try { ((HttpSessionBindingListener) value).valueBound(event); } catch (Throwable t) { manager.getContext().getLogger().error(sm.getString("standardSession.bindingEvent"), t); } } } // Replace or add this attribute Object unbound = attributes.put(name, value); // Call the valueUnbound() method if necessary if (notify && unbound instanceof HttpSessionBindingListener) { // Don't call any notification if replacing with the same value // unless configured to do so if (unbound != value || manager.getNotifyBindingListenerOnUnchangedValue()) { try { ((HttpSessionBindingListener) unbound) .valueUnbound(new HttpSessionBindingEvent(getSession(), name)); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); manager.getContext().getLogger().error(sm.getString("standardSession.bindingEvent"), t); } } } if (!notify) { return; } // Notify interested application event listeners Object listeners[] = context.getApplicationEventListeners(); if (listeners == null) { return; } for (Object o : listeners) { if (!(o instanceof HttpSessionAttributeListener)) { continue; } HttpSessionAttributeListener listener = (HttpSessionAttributeListener) o; try { if (unbound != null) { if (unbound != value || manager.getNotifyAttributeListenerOnUnchangedValue()) { context.fireContainerEvent("beforeSessionAttributeReplaced", listener); if (event == null) { event = new HttpSessionBindingEvent(getSession(), name, unbound); } listener.attributeReplaced(event); context.fireContainerEvent("afterSessionAttributeReplaced", listener); } } else { context.fireContainerEvent("beforeSessionAttributeAdded", listener); if (event == null) { event = new HttpSessionBindingEvent(getSession(), name, value); } listener.attributeAdded(event); context.fireContainerEvent("afterSessionAttributeAdded", listener); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); try { if (unbound != null) { if (unbound != value || manager.getNotifyAttributeListenerOnUnchangedValue()) { context.fireContainerEvent("afterSessionAttributeReplaced", listener); } } else { context.fireContainerEvent("afterSessionAttributeAdded", listener); } } catch (Exception e) { // Ignore } manager.getContext().getLogger().error(sm.getString("standardSession.attributeEvent"), t); } } } // ------------------------------------------ HttpSession Protected Methods /** * @return the isValid flag for this session without any expiration check. */ protected boolean isValidInternal() { return this.isValid; } /** * {@inheritDoc} *

* This implementation simply checks the value for serializability. Sub-classes might use other distribution * technology not based on serialization and can override this check. */ @Override public boolean isAttributeDistributable(String name, Object value) { return value instanceof Serializable; } /** * Read a serialized version of this session object from the specified object input stream. *

* IMPLEMENTATION NOTE: The reference to the owning Manager is not restored by this method, and must be set * explicitly. * * @param stream The input stream to read from * * @exception ClassNotFoundException if an unknown class is specified * @exception IOException if an input/output error occurs */ protected void doReadObject(ObjectInputStream stream) throws ClassNotFoundException, IOException { // Deserialize the scalar instance variables (except Manager) authType = null; // Transient (may be set later) creationTime = ((Long) stream.readObject()).longValue(); lastAccessedTime = ((Long) stream.readObject()).longValue(); maxInactiveInterval = ((Integer) stream.readObject()).intValue(); isNew = ((Boolean) stream.readObject()).booleanValue(); isValid = ((Boolean) stream.readObject()).booleanValue(); thisAccessedTime = ((Long) stream.readObject()).longValue(); principal = null; // Transient (may be set later) // setId((String) stream.readObject()); id = (String) stream.readObject(); if (manager.getContext().getLogger().isDebugEnabled()) { manager.getContext().getLogger().debug("readObject() loading session " + id); } if (notes == null) { notes = new ConcurrentHashMap<>(); } /* * The next object read could either be the number of attributes (Integer) or if authentication information is * persisted then: - authType (String) - always present - Principal object - always present - expected session * ID - present if persistAuthenticationNotes == true - saved request - present if persistAuthenticationNotes == * true * * Note: Some, all or none of the above objects may be null */ Object nextObject = stream.readObject(); if (!(nextObject instanceof Integer)) { // Not an Integer so the next two objects will be authType and // Principal setAuthType((String) nextObject); try { setPrincipal((Principal) stream.readObject()); } catch (ClassNotFoundException | ObjectStreamException e) { String msg = sm.getString("standardSession.principalNotDeserializable", id); if (manager.getContext().getLogger().isDebugEnabled()) { manager.getContext().getLogger().debug(msg, e); } else { manager.getContext().getLogger().warn(msg); } throw e; } nextObject = stream.readObject(); if (!(nextObject instanceof Integer)) { // Not an Integer so the next two objects will be // 'expected session ID' and 'saved request' if (nextObject != null) { notes.put(org.apache.catalina.authenticator.Constants.SESSION_ID_NOTE, nextObject); } nextObject = stream.readObject(); if (nextObject != null) { notes.put(org.apache.catalina.authenticator.Constants.FORM_REQUEST_NOTE, nextObject); } // Next object will be the number of attributes nextObject = stream.readObject(); } } // Deserialize the attribute count and attribute values if (attributes == null) { attributes = new ConcurrentHashMap<>(); } int n = ((Integer) nextObject).intValue(); boolean isValidSave = isValid; isValid = true; for (int i = 0; i < n; i++) { String name = (String) stream.readObject(); final Object value; try { value = stream.readObject(); } catch (WriteAbortedException wae) { if (wae.getCause() instanceof NotSerializableException) { String msg = sm.getString("standardSession.notDeserializable", name, id); if (manager.getContext().getLogger().isDebugEnabled()) { manager.getContext().getLogger().debug(msg, wae); } else { manager.getContext().getLogger().warn(msg); } // Skip non serializable attributes continue; } throw wae; } if (manager.getContext().getLogger().isDebugEnabled()) { manager.getContext().getLogger().debug(" loading attribute '" + name + "' with value '" + value + "'"); } // Handle the case where the filter configuration was changed while // the web application was stopped. if (exclude(name, value)) { continue; } // ConcurrentHashMap does not allow null keys or values if (null != value) { attributes.put(name, value); } } isValid = isValidSave; if (listeners == null) { listeners = new ArrayList<>(); } } /** * Write a serialized version of this session object to the specified object output stream. *

* IMPLEMENTATION NOTE: The owning Manager will not be stored in the serialized representation of this * Session. After calling readObject(), you must set the associated Manager explicitly. *

* IMPLEMENTATION NOTE: Any attribute that is not Serializable will be unbound from the session, with * appropriate actions if it implements HttpSessionBindingListener. If you do not want any such attributes, be sure * the distributable property of the associated Manager is set to true. * * @param stream The output stream to write to * * @exception IOException if an input/output error occurs */ @SuppressWarnings("deprecation") protected void doWriteObject(ObjectOutputStream stream) throws IOException { // Write the scalar instance variables (except Manager) stream.writeObject(Long.valueOf(creationTime)); stream.writeObject(Long.valueOf(lastAccessedTime)); stream.writeObject(Integer.valueOf(maxInactiveInterval)); stream.writeObject(Boolean.valueOf(isNew)); stream.writeObject(Boolean.valueOf(isValid)); stream.writeObject(Long.valueOf(thisAccessedTime)); stream.writeObject(id); if (manager.getContext().getLogger().isDebugEnabled()) { manager.getContext().getLogger().debug("writeObject() storing session " + id); } // Gather authentication information (if configured) String sessionAuthType = null; Principal sessionPrincipal = null; String expectedSessionId = null; SavedRequest savedRequest = null; if (getPersistAuthentication()) { sessionAuthType = getAuthType(); sessionPrincipal = getPrincipal(); if (sessionPrincipal != null && !(sessionPrincipal instanceof Serializable)) { sessionPrincipal = null; manager.getContext().getLogger().warn(sm.getString("standardSession.principalNotSerializable", id)); } expectedSessionId = (String) notes.get(org.apache.catalina.authenticator.Constants.SESSION_ID_NOTE); savedRequest = (SavedRequest) notes.get(org.apache.catalina.authenticator.Constants.FORM_REQUEST_NOTE); } // Write authentication information (may be null values) stream.writeObject(sessionAuthType); try { stream.writeObject(sessionPrincipal); } catch (NotSerializableException e) { manager.getContext().getLogger().warn(sm.getString("standardSession.principalNotSerializable", id), e); } if (manager instanceof ManagerBase && ((ManagerBase) manager).getPersistAuthenticationNotes()) { // Write the notes associated with authentication. Without these, // authentication can fail if there is a persist/restore during // FORM authentication stream.writeObject(expectedSessionId); stream.writeObject(savedRequest); } // Accumulate the names of serializable and non-serializable attributes String keys[] = keys(); List saveNames = new ArrayList<>(); List saveValues = new ArrayList<>(); for (String key : keys) { Object value = attributes.get(key); if (value == null) { continue; } else if (isAttributeDistributable(key, value) && !exclude(key, value)) { saveNames.add(key); saveValues.add(value); } else { removeAttributeInternal(key, true); } } // Serialize the attribute count and the Serializable attributes int n = saveNames.size(); stream.writeObject(Integer.valueOf(n)); for (int i = 0; i < n; i++) { stream.writeObject(saveNames.get(i)); try { stream.writeObject(saveValues.get(i)); if (manager.getContext().getLogger().isDebugEnabled()) { manager.getContext().getLogger().debug( " storing attribute '" + saveNames.get(i) + "' with value '" + saveValues.get(i) + "'"); } } catch (NotSerializableException e) { manager.getContext().getLogger() .warn(sm.getString("standardSession.notSerializable", saveNames.get(i), id), e); } } } /** * Return whether authentication information shall be persisted or not. * * @return {@code true}, if authentication information shall be persisted; {@code false} otherwise */ private boolean getPersistAuthentication() { if (manager instanceof ManagerBase) { return ((ManagerBase) manager).getPersistAuthentication(); } return false; } /** * Should the given session attribute be excluded? This implementation checks: *
    *
  • {@link Constants#excludedAttributeNames}
  • *
  • {@link Manager#willAttributeDistribute(String, Object)}
  • *
* Note: This method deliberately does not check {@link #isAttributeDistributable(String, Object)} which is kept * separate to support the checks required in {@link #setAttribute(String, Object, boolean)} * * @param name The attribute name * @param value The attribute value * * @return {@code true} if the attribute should be excluded from distribution, otherwise {@code false} */ protected boolean exclude(String name, Object value) { if (Constants.excludedAttributeNames.contains(name)) { return true; } // Manager is required for remaining check Manager manager = getManager(); if (manager == null) { // Manager may be null during replication of new sessions in a // cluster. Avoid the NPE. return false; } // Last check so use a short-cut return !manager.willAttributeDistribute(name, value); } // ------------------------------------------------------ Protected Methods /** * Notify all session event listeners that a particular event has occurred for this Session. The default * implementation performs this notification synchronously using the calling thread. * * @param type Event type * @param data Event data */ public void fireSessionEvent(String type, Object data) { if (listeners.size() < 1) { return; } SessionEvent event = new SessionEvent(this, type, data); SessionListener list[] = new SessionListener[0]; synchronized (listeners) { list = listeners.toArray(list); } for (SessionListener sessionListener : list) { sessionListener.sessionEvent(event); } } /** * @return the names of all currently defined session attributes as an array of Strings. If there are no defined * attributes, a zero-length array is returned. */ protected String[] keys() { return attributes.keySet().toArray(EMPTY_ARRAY); } /** * Remove the object bound with the specified name from this session. If the session does not have an object bound * with this name, this method does nothing. *

* After this method executes, and if the object implements HttpSessionBindingListener, the container * calls valueUnbound() on the object. * * @param name Name of the object to remove from this session. * @param notify Should we notify interested listeners that this attribute is being removed? */ protected void removeAttributeInternal(String name, boolean notify) { // Avoid NPE if (name == null) { return; } // Remove this attribute from our collection Object value = attributes.remove(name); // Do we need to do valueUnbound() and attributeRemoved() notification? if (!notify || (value == null)) { return; } // Call the valueUnbound() method if necessary HttpSessionBindingEvent event = null; if (value instanceof HttpSessionBindingListener) { event = new HttpSessionBindingEvent(getSession(), name, value); ((HttpSessionBindingListener) value).valueUnbound(event); } // Notify interested application event listeners Context context = manager.getContext(); Object listeners[] = context.getApplicationEventListeners(); if (listeners == null) { return; } for (Object o : listeners) { if (!(o instanceof HttpSessionAttributeListener)) { continue; } HttpSessionAttributeListener listener = (HttpSessionAttributeListener) o; try { context.fireContainerEvent("beforeSessionAttributeRemoved", listener); if (event == null) { event = new HttpSessionBindingEvent(getSession(), name, value); } listener.attributeRemoved(event); context.fireContainerEvent("afterSessionAttributeRemoved", listener); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); try { context.fireContainerEvent("afterSessionAttributeRemoved", listener); } catch (Exception e) { // Ignore } manager.getContext().getLogger().error(sm.getString("standardSession.attributeEvent"), t); } } } private static class PrivilegedNewSessionFacade implements PrivilegedAction { private final HttpSession session; PrivilegedNewSessionFacade(HttpSession session) { this.session = session; } @Override public StandardSessionFacade run() { return new StandardSessionFacade(session); } } } // ------------------------------------------------------------ Protected Class /** * This class is a dummy implementation of the HttpSessionContext interface, to conform to the requirement * that such an object be returned when HttpSession.getSessionContext() is called. * * @author Craig R. McClanahan * * @deprecated As of Java Servlet API 2.1 with no replacement. The interface will be removed in a future version of this * API. */ @Deprecated final class StandardSessionContext implements javax.servlet.http.HttpSessionContext { private static final List emptyString = Collections.emptyList(); /** * Return the session identifiers of all sessions defined within this context. * * @deprecated As of Java Servlet API 2.1 with no replacement. This method must return an empty * Enumeration and will be removed in a future version of the API. */ @Override @Deprecated public Enumeration getIds() { return Collections.enumeration(emptyString); } /** * Return the HttpSession associated with the specified session identifier. * * @param id Session identifier for which to look up a session * * @deprecated As of Java Servlet API 2.1 with no replacement. This method must return null and will be removed in a * future version of the API. */ @Override @Deprecated public HttpSession getSession(String id) { return null; } }