net.lightbody.bmp.proxy.jetty.jetty.servlet.AbstractSessionManager.java Source code

Java tutorial

Introduction

Here is the source code for net.lightbody.bmp.proxy.jetty.jetty.servlet.AbstractSessionManager.java

Source

// ========================================================================
// $Id: AbstractSessionManager.java,v 1.53 2006/11/22 20:01:10 gregwilkins Exp $
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// 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 net.lightbody.bmp.proxy.jetty.jetty.servlet;

import net.lightbody.bmp.proxy.jetty.http.HttpOnlyCookie;
import net.lightbody.bmp.proxy.jetty.log.LogFactory;
import net.lightbody.bmp.proxy.jetty.util.LazyList;
import net.lightbody.bmp.proxy.jetty.util.LogSupport;
import net.lightbody.bmp.proxy.jetty.util.MultiMap;
import org.apache.commons.logging.Log;

import javax.servlet.ServletContext;
import javax.servlet.http.*;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;

/* ------------------------------------------------------------ */
/** An Abstract implementation of SessionManager.
 * The partial implementation of SessionManager interface provides
 * the majority of the handling required to implement a
 * SessionManager.  Concrete implementations of SessionManager based
 * on AbstractSessionManager need only implement the newSession method
 * to return a specialized version of the Session inner class that
 * provides an attribute Map.
 * <p>
 * If the property
 * org.mortbay.jetty.servlet.AbstractSessionManager.23Notifications is set to
 * true, the 2.3 servlet spec notification style will be used.
 * <p>
 * @version $Id: AbstractSessionManager.java,v 1.53 2006/11/22 20:01:10 gregwilkins Exp $
 * @author Greg Wilkins (gregw)
 */
public abstract class AbstractSessionManager implements SessionManager {
    private static Log log = LogFactory.getLog(AbstractSessionManager.class);

    /* ------------------------------------------------------------ */
    public final static int __distantFuture = 60 * 60 * 24 * 7 * 52 * 20;
    private final static String __NEW_SESSION_ID = "net.lightbody.bmp.proxy.jetty.jetty.newSessionId";

    /* ------------------------------------------------------------ */
    /* global Map of ID to session */
    protected static MultiMap __allSessions = new MultiMap();

    /* ------------------------------------------------------------ */
    // Setting of max inactive interval for new sessions
    // -1 means no timeout
    private int _dftMaxIdleSecs = -1;
    private int _scavengePeriodMs = 30000;
    private String _workerName;
    protected transient ArrayList _sessionListeners = new ArrayList();
    protected transient ArrayList _sessionAttributeListeners = new ArrayList();
    protected transient Map _sessions;
    protected transient Random _random;
    protected transient boolean _weakRandom;
    protected transient ServletHandler _handler;
    protected int _minSessions = 0;
    protected int _maxSessions = 0;
    protected boolean _crossContextSessionIDs = false;
    protected boolean _secureCookies = false;
    protected boolean _httpOnly = false;
    protected boolean _invalidateGlobal = true;

    private transient SessionScavenger _scavenger = null;

    /* ------------------------------------------------------------ */
    public AbstractSessionManager() {
        this(null);
    }

    /* ------------------------------------------------------------ */
    public AbstractSessionManager(Random random) {
        _random = random;
        _weakRandom = false;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return True if requested session ID are first considered for new
     * @deprecated use getCrossContextSessionIDs
     * session IDs
     */
    public boolean getUseRequestedId() {
        return _crossContextSessionIDs;
    }

    /* ------------------------------------------------------------ */
    /** Set Use Requested ID.
     * @param useRequestedId True if requested session ID are first considered for new
     * @deprecated use setCrossContextSessionIDs
     * session IDs
     */
    public void setUseRequestedId(boolean useRequestedId) {
        _crossContextSessionIDs = useRequestedId;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return True if cross context session IDs are first considered for new
     * session IDs
     */
    public boolean getCrossContextSessionIDs() {
        return _crossContextSessionIDs;
    }

    /* ------------------------------------------------------------ */
    /** Set Cross Context sessions IDs
     * This option activates a mode where a requested session ID can be used to create a 
     * new session. This facilitates the sharing of session cookies when cross context
     * dispatches use sessions.   
     * 
     * @param useRequestedId True if cross context session ID are first considered for new
     * session IDs
     */
    public void setCrossContextSessionIDs(boolean useRequestedId) {
        _crossContextSessionIDs = useRequestedId;
    }

    /* ------------------------------------------------------------ */
    public void initialize(ServletHandler handler) {
        _handler = handler;
    }

    /* ------------------------------------------------------------ */
    public Map getSessionMap() {
        return Collections.unmodifiableMap(_sessions);
    }

    /* ------------------------------------------------------------ */
    public int getSessions() {
        return _sessions.size();
    }

    /* ------------------------------------------------------------ */
    public int getMinSessions() {
        return _minSessions;
    }

    /* ------------------------------------------------------------ */
    public int getMaxSessions() {
        return _maxSessions;
    }

    /* ------------------------------------------------------------ */
    public void resetStats() {
        _minSessions = _sessions.size();
        _maxSessions = _sessions.size();
    }

    /* ------------------------------------------------------------ */
    /* new Session ID.
     * If the request has a requestedSessionID which is unique, that is used.
     * The session ID is created as a unique random long base 36.
     * If the request has a jvmRoute attribute, that is appended as a
     * worker tag, else any worker tag set on the manager is appended.
     * @param request 
     * @param created 
     * @return Session ID.
     */
    private String newSessionId(HttpServletRequest request, long created) {
        synchronized (__allSessions) {
            // A requested session ID can only be used if it is in the global map of
            // ID but not in this contexts map.  Ie it is an ID in use by another context
            // in this server and thus we are doing a cross context dispatch.
            if (_crossContextSessionIDs) {
                String requested_id = (String) request.getAttribute(__NEW_SESSION_ID);
                if (requested_id == null)
                    requested_id = request.getRequestedSessionId();
                if (requested_id != null && requested_id != null && __allSessions.containsKey(requested_id)
                        && !_sessions.containsKey(requested_id))
                    return requested_id;
            }

            // pick a new unique ID!
            String id = null;
            while (id == null || id.length() == 0 || __allSessions.containsKey(id)) {
                long r = _weakRandom
                        ? (hashCode() ^ Runtime.getRuntime().freeMemory() ^ _random.nextInt()
                                ^ (((long) request.hashCode()) << 32))
                        : _random.nextLong();
                r ^= created;
                if (request != null && request.getRemoteAddr() != null)
                    r ^= request.getRemoteAddr().hashCode();
                if (r < 0)
                    r = -r;
                id = Long.toString(r, 36);

                String worker = (String) request.getAttribute("net.lightbody.bmp.proxy.jetty.http.ajp.JVMRoute");
                if (worker != null)
                    id += "." + worker;
                else if (_workerName != null)
                    id += "." + _workerName;
            }
            return id;
        }
    }

    /* ------------------------------------------------------------ */
    public HttpSession getHttpSession(String id) {
        synchronized (this) {
            return (HttpSession) _sessions.get(id);
        }
    }

    /* ------------------------------------------------------------ */
    public HttpSession newHttpSession(HttpServletRequest request) {
        Session session = newSession(request);
        session.setMaxInactiveInterval(_dftMaxIdleSecs);
        synchronized (__allSessions) {
            synchronized (this) {
                _sessions.put(session.getId(), session);
                __allSessions.add(session.getId(), session);
                if (_sessions.size() > this._maxSessions)
                    this._maxSessions = _sessions.size();
            }
        }

        HttpSessionEvent event = new HttpSessionEvent(session);

        for (int i = 0; i < _sessionListeners.size(); i++)
            ((HttpSessionListener) _sessionListeners.get(i)).sessionCreated(event);

        if (getCrossContextSessionIDs())
            request.setAttribute(__NEW_SESSION_ID, session.getId());
        return session;
    }

    /* ------------------------------------------------------------ */
    public Cookie getSessionCookie(HttpSession session, boolean requestIsSecure) {
        if (_handler.isUsingCookies()) {
            Cookie cookie = _handler.getSessionManager().getHttpOnly()
                    ? new HttpOnlyCookie(SessionManager.__SessionCookie, session.getId())
                    : new Cookie(SessionManager.__SessionCookie, session.getId());
            String domain = _handler.getServletContext().getInitParameter(SessionManager.__SessionDomain);
            String maxAge = _handler.getServletContext().getInitParameter(SessionManager.__MaxAge);
            String path = _handler.getServletContext().getInitParameter(SessionManager.__SessionPath);
            if (path == null)
                path = getCrossContextSessionIDs() ? "/" : _handler.getHttpContext().getContextPath();
            if (path == null || path.length() == 0)
                path = "/";

            if (domain != null)
                cookie.setDomain(domain);
            if (maxAge != null)
                cookie.setMaxAge(Integer.parseInt(maxAge));
            else
                cookie.setMaxAge(-1);

            cookie.setSecure(requestIsSecure && getSecureCookies());
            cookie.setPath(path);

            return cookie;
        }
        return null;
    }

    /* ------------------------------------------------------------ */
    protected abstract Session newSession(HttpServletRequest request);

    /* ------------------------------------------------------------ */
    /** Get the workname.
     * If set, the workername is dot appended to the session ID
     * and can be used to assist session affinity in a load balancer.
     * @return String or null
     */
    public String getWorkerName() {
        return _workerName;
    }

    /* ------------------------------------------------------------ */
    /** Set the workname.
     * If set, the workername is dot appended to the session ID
     * and can be used to assist session affinity in a load balancer.
     * @param workerName 
     */
    public void setWorkerName(String workerName) {
        _workerName = workerName;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return seconds 
     */
    public int getMaxInactiveInterval() {
        return _dftMaxIdleSecs;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @param seconds 
     */
    public void setMaxInactiveInterval(int seconds) {
        _dftMaxIdleSecs = seconds;
        if (_dftMaxIdleSecs > 0 && _scavengePeriodMs > _dftMaxIdleSecs * 100)
            setScavengePeriod((_dftMaxIdleSecs + 9) / 10);
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return seconds 
     */
    public int getScavengePeriod() {
        return _scavengePeriodMs / 1000;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @param seconds 
     */
    public void setScavengePeriod(int seconds) {
        if (seconds == 0)
            seconds = 60;

        int old_period = _scavengePeriodMs;
        int period = seconds * 1000;
        if (period > 60000)
            period = 60000;
        if (period < 1000)
            period = 1000;

        if (period != old_period) {
            synchronized (this) {
                _scavengePeriodMs = period;
                if (_scavenger != null)
                    _scavenger.interrupt();
            }
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the httpOnly.
     */
    public boolean getHttpOnly() {
        return _httpOnly;
    }

    /* ------------------------------------------------------------ */
    /**
     * @param httpOnly The httpOnly to set.
     */
    public void setHttpOnly(boolean httpOnly) {
        _httpOnly = httpOnly;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the secureCookies.
     */
    public boolean getSecureCookies() {
        return _secureCookies;
    }

    /* ------------------------------------------------------------ */
    /**
     * @param secureCookies The secureCookies to set.
     */
    public void setSecureCookies(boolean secureCookies) {
        _secureCookies = secureCookies;
    }

    /* ------------------------------------------------------------ */
    public boolean isInvalidateGlobal() {
        return _invalidateGlobal;
    }

    /* ------------------------------------------------------------ */
    /**
     * @param global True if session invalidation should be global.
     * ie Sessions in other contexts with the same ID (linked by cross context dispatch
     * or shared session cookie) are invalidated as a group.
     */
    public void setInvalidateGlobal(boolean global) {
        _invalidateGlobal = global;
    }

    /* ------------------------------------------------------------ */
    public void addEventListener(EventListener listener) throws IllegalArgumentException {

        if (listener instanceof HttpSessionAttributeListener)
            _sessionAttributeListeners.add(listener);
        if (listener instanceof HttpSessionListener)
            _sessionListeners.add(listener);
    }

    /* ------------------------------------------------------------ */
    public void removeEventListener(EventListener listener) {
        if (listener instanceof HttpSessionAttributeListener)
            _sessionAttributeListeners.remove(listener);
        if (listener instanceof HttpSessionListener)
            _sessionListeners.remove(listener);
    }

    /* ------------------------------------------------------------ */
    public boolean isStarted() {
        return _scavenger != null;
    }

    /* ------------------------------------------------------------ */
    public void start() throws Exception {
        if (_random == null) {
            log.debug("New random session seed");
            try {
                _random = SecureRandom.getInstance("SHA1PRNG");
            } catch (NoSuchAlgorithmException e) {
                log.warn("Could not generate SecureRandom for session-id randomness", e);
                _random = new Random();
                _weakRandom = true;
            }
            _random.setSeed(_random.nextLong() ^ System.currentTimeMillis() ^ hashCode()
                    ^ Runtime.getRuntime().freeMemory());
        }

        if (_sessions == null)
            _sessions = new HashMap();

        // Start the session scavenger if we haven't already
        if (_scavenger == null) {
            _scavenger = new SessionScavenger();
            _scavenger.start();
        }
    }

    /* ------------------------------------------------------------ */
    public void stop() {
        // Invalidate all sessions to cause unbind events
        ArrayList sessions = new ArrayList(_sessions.values());
        for (Iterator i = sessions.iterator(); i.hasNext();) {
            Session session = (Session) i.next();
            session.invalidate();
        }
        _sessions.clear();

        // stop the scavenger
        SessionScavenger scavenger = _scavenger;
        _scavenger = null;
        if (scavenger != null)
            scavenger.interrupt();
    }

    /* -------------------------------------------------------------- */
    /** Find sessions that have timed out and invalidate them.
     *  This runs in the SessionScavenger thread.
     */
    private void scavenge() {
        Thread thread = Thread.currentThread();
        ClassLoader old_loader = thread.getContextClassLoader();
        try {
            if (_handler == null)
                return;

            ClassLoader loader = _handler.getClassLoader();
            if (loader != null)
                thread.setContextClassLoader(loader);

            long now = System.currentTimeMillis();

            // Since Hashtable enumeration is not safe over deletes,
            // we build a list of stale sessions, then go back and invalidate them
            Object stale = null;

            synchronized (AbstractSessionManager.this) {
                // For each session
                for (Iterator i = _sessions.values().iterator(); i.hasNext();) {
                    Session session = (Session) i.next();
                    long idleTime = session._maxIdleMs;
                    if (idleTime > 0 && session._accessed + idleTime < now) {
                        // Found a stale session, add it to the list
                        stale = LazyList.add(stale, session);
                    }
                }
            }

            // Remove the stale sessions
            for (int i = LazyList.size(stale); i-- > 0;) {
                // check it has not been accessed in the meantime
                Session session = (Session) LazyList.get(stale, i);
                long idleTime = session._maxIdleMs;
                if (idleTime > 0 && session._accessed + idleTime < System.currentTimeMillis()) {
                    session.invalidate();
                    int nbsess = this._sessions.size();
                    if (nbsess < this._minSessions)
                        this._minSessions = nbsess;
                }
            }
        } finally {
            thread.setContextClassLoader(old_loader);
        }
    }

    /* ------------------------------------------------------------ */
    public Random getRandom() {
        return _random;
    }

    /* ------------------------------------------------------------ */
    public void setRandom(Random random) {
        _random = random;
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* -------------------------------------------------------------- */
    /** SessionScavenger is a background thread that kills off old sessions */
    class SessionScavenger extends Thread {
        public void run() {
            int period = -1;
            try {
                while (isStarted()) {
                    try {
                        if (period != _scavengePeriodMs) {
                            if (log.isDebugEnabled())
                                log.debug("Session scavenger period = " + _scavengePeriodMs / 1000 + "s");
                            period = _scavengePeriodMs;
                        }
                        sleep(period > 1000 ? period : 1000);
                        AbstractSessionManager.this.scavenge();
                    } catch (InterruptedException ex) {
                        continue;
                    } catch (Error e) {
                        log.warn(LogSupport.EXCEPTION, e);
                    } catch (Exception e) {
                        log.warn(LogSupport.EXCEPTION, e);
                    }
                }
            } finally {
                AbstractSessionManager.this._scavenger = null;
                log.debug("Session scavenger exited");
            }
        }

        SessionScavenger() {
            super("SessionScavenger");
            setDaemon(true);
        }

    } // SessionScavenger

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    public abstract class Session implements SessionManager.Session {
        Map _values;
        boolean _invalid = false;
        boolean _newSession = true;
        long _created = System.currentTimeMillis();
        long _accessed = _created;
        long _maxIdleMs = _dftMaxIdleSecs * 1000;
        String _id;

        /* ------------------------------------------------------------- */
        protected Session(HttpServletRequest request) {
            _id = newSessionId(request, _created);
            if (_dftMaxIdleSecs >= 0)
                _maxIdleMs = _dftMaxIdleSecs * 1000;
        }

        /* ------------------------------------------------------------ */
        protected abstract Map newAttributeMap();

        /* ------------------------------------------------------------ */
        public void access() {
            _newSession = false;
            _accessed = System.currentTimeMillis();
        }

        /* ------------------------------------------------------------ */
        public boolean isValid() {
            return !_invalid;
        }

        /* ------------------------------------------------------------ */
        public ServletContext getServletContext() {
            return _handler.getServletContext();
        }

        /* ------------------------------------------------------------- */
        public String getId() throws IllegalStateException {
            return _id;
        }

        /* ------------------------------------------------------------- */
        public long getCreationTime() throws IllegalStateException {
            if (_invalid)
                throw new IllegalStateException();
            return _created;
        }

        /* ------------------------------------------------------------- */
        public long getLastAccessedTime() throws IllegalStateException {
            if (_invalid)
                throw new IllegalStateException();
            return _accessed;
        }

        /* ------------------------------------------------------------- */
        public int getMaxInactiveInterval() {
            if (_invalid)
                throw new IllegalStateException();
            return (int) (_maxIdleMs / 1000);
        }

        /* ------------------------------------------------------------- */
        /**
         * @deprecated
         */
        public HttpSessionContext getSessionContext() throws IllegalStateException {
            if (_invalid)
                throw new IllegalStateException();
            return SessionContext.NULL_IMPL;
        }

        /* ------------------------------------------------------------- */
        public void setMaxInactiveInterval(int secs) {
            _maxIdleMs = (long) secs * 1000;
            if (_maxIdleMs > 0 && (_maxIdleMs / 10) < _scavengePeriodMs)
                AbstractSessionManager.this.setScavengePeriod((secs + 9) / 10);
        }

        /* ------------------------------------------------------------- */
        public void invalidate() throws IllegalStateException {
            if (log.isDebugEnabled())
                log.debug("Invalidate session " + getId() + " in " + _handler.getHttpContext());
            try {
                // Notify listeners and unbind values
                synchronized (this) {
                    if (_invalid)
                        throw new IllegalStateException();

                    if (_sessionListeners != null) {
                        HttpSessionEvent event = new HttpSessionEvent(this);
                        for (int i = _sessionListeners.size(); i-- > 0;)
                            ((HttpSessionListener) _sessionListeners.get(i)).sessionDestroyed(event);
                    }

                    if (_values != null) {
                        Iterator iter = _values.keySet().iterator();
                        while (iter.hasNext()) {
                            String key = (String) iter.next();
                            Object value = _values.get(key);
                            iter.remove();
                            unbindValue(key, value);

                            if (_sessionAttributeListeners.size() > 0) {
                                HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, key, value);

                                for (int i = 0; i < _sessionAttributeListeners.size(); i++) {
                                    ((HttpSessionAttributeListener) _sessionAttributeListeners.get(i))
                                            .attributeRemoved(event);
                                }
                            }
                        }
                    }
                }
            } finally {
                // Remove session from context and global maps
                synchronized (__allSessions) {
                    synchronized (_sessions) {
                        _invalid = true;
                        _sessions.remove(getId());
                        __allSessions.removeValue(getId(), this);

                        if (isInvalidateGlobal()) {
                            // Don't iterate as other sessions may also be globally invalidating
                            while (__allSessions.containsKey(getId())) {
                                Session session = (Session) __allSessions.getValue(getId(), 0);
                                session.invalidate();
                            }
                        }
                    }
                }
            }
        }

        /* ------------------------------------------------------------- */
        public boolean isNew() throws IllegalStateException {
            if (_invalid)
                throw new IllegalStateException();
            return _newSession;
        }

        /* ------------------------------------------------------------ */
        public synchronized Object getAttribute(String name) {
            if (_invalid)
                throw new IllegalStateException();
            if (_values == null)
                return null;
            return _values.get(name);
        }

        /* ------------------------------------------------------------ */
        public synchronized Enumeration getAttributeNames() {
            if (_invalid)
                throw new IllegalStateException();
            List names = _values == null ? Collections.EMPTY_LIST : new ArrayList(_values.keySet());
            return Collections.enumeration(names);
        }

        /* ------------------------------------------------------------ */
        public synchronized void setAttribute(String name, Object value) {
            if (_invalid)
                throw new IllegalStateException();
            if (_values == null)
                _values = newAttributeMap();
            Object oldValue = _values.put(name, value);

            if (value == null || !value.equals(oldValue)) {
                unbindValue(name, oldValue);
                bindValue(name, value);

                if (_sessionAttributeListeners.size() > 0) {
                    HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, name,
                            oldValue == null ? value : oldValue);

                    for (int i = 0; i < _sessionAttributeListeners.size(); i++) {
                        HttpSessionAttributeListener l = (HttpSessionAttributeListener) _sessionAttributeListeners
                                .get(i);

                        if (oldValue == null)
                            l.attributeAdded(event);
                        else if (value == null)
                            l.attributeRemoved(event);
                        else
                            l.attributeReplaced(event);
                    }
                }
            }
        }

        /* ------------------------------------------------------------ */
        public synchronized void removeAttribute(String name) {
            if (_invalid)
                throw new IllegalStateException();
            if (_values == null)
                return;

            Object old = _values.remove(name);
            if (old != null) {
                unbindValue(name, old);
                if (_sessionAttributeListeners.size() > 0) {
                    HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, name, old);

                    for (int i = 0; i < _sessionAttributeListeners.size(); i++) {
                        HttpSessionAttributeListener l = (HttpSessionAttributeListener) _sessionAttributeListeners
                                .get(i);
                        l.attributeRemoved(event);
                    }
                }
            }
        }

        /* ------------------------------------------------------------- */
        /**
         * @deprecated    As of Version 2.2, this method is
         *       replaced by {@link #getAttribute}
         */
        public Object getValue(String name) throws IllegalStateException {
            return getAttribute(name);
        }

        /* ------------------------------------------------------------- */
        /**
         * @deprecated    As of Version 2.2, this method is
         *       replaced by {@link #getAttributeNames}
         */
        public synchronized String[] getValueNames() throws IllegalStateException {
            if (_invalid)
                throw new IllegalStateException();
            if (_values == null)
                return new String[0];
            String[] a = new String[_values.size()];
            return (String[]) _values.keySet().toArray(a);
        }

        /* ------------------------------------------------------------- */
        /**
         * @deprecated    As of Version 2.2, this method is
         *       replaced by {@link #setAttribute}
         */
        public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException {
            setAttribute(name, value);
        }

        /* ------------------------------------------------------------- */
        /**
         * @deprecated    As of Version 2.2, this method is
         *       replaced by {@link #removeAttribute}
         */
        public void removeValue(java.lang.String name) throws IllegalStateException {
            removeAttribute(name);
        }

        /* ------------------------------------------------------------- */
        /** If value implements HttpSessionBindingListener, call valueBound() */
        private void bindValue(java.lang.String name, Object value) {
            if (value != null && value instanceof HttpSessionBindingListener)
                ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name));
        }

        /* ------------------------------------------------------------- */
        /** If value implements HttpSessionBindingListener, call valueUnbound() */
        private void unbindValue(java.lang.String name, Object value) {
            if (value != null && value instanceof HttpSessionBindingListener)
                ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name));
        }
    }

}