// ========================================================================
// 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 org.mortbay.jetty.servlet;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
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.HttpSessionContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.mortbay.component.AbstractLifeCycle;
import org.mortbay.jetty.HttpOnlyCookie;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.SessionIdManager;
import org.mortbay.jetty.SessionManager;
import org.mortbay.jetty.handler.ContextHandler;
import org.mortbay.util.LazyList;

/* ------------------------------------------------------------ */
/**
 * 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>
 * 
 * @author Greg Wilkins (gregw)
 */
public abstract class AbstractSessionManager extends AbstractLifeCycle implements SessionManager
{
    /* ------------------------------------------------------------ */
    public final static int __distantFuture=60*60*24*7*52*20;

    private static final HttpSessionContext __nullSessionContext=new NullSessionContext();

    private boolean _usingCookies=true;
    
    /* ------------------------------------------------------------ */
    // Setting of max inactive interval for new sessions
    // -1 means no timeout
    protected int _dftMaxIdleSecs=-1;
    protected SessionHandler _sessionHandler;
    protected boolean _httpOnly=false;
    protected int _maxSessions=0;

    protected int _minSessions=0;
    protected SessionIdManager _sessionIdManager;
    protected boolean _secureCookies=false;
    protected Object _sessionAttributeListeners;
    protected Object _sessionListeners;
    
    protected ClassLoader _loader;
    protected ContextHandler.SContext _context;
    protected String _sessionCookie=__DefaultSessionCookie;
    protected String _sessionURL=__DefaultSessionURL;
    protected String _sessionURLPrefix=";"+_sessionURL+"=";
    protected String _sessionDomain;
    protected String _sessionPath;
    protected int _maxCookieAge=-1;
    protected int _refreshCookieAge;
    protected boolean _nodeIdInSessionId;

    /* ------------------------------------------------------------ */
    public AbstractSessionManager()
    {
    }

    /* ------------------------------------------------------------ */
    public Cookie access(HttpSession session,boolean secure)
    {
        long now=System.currentTimeMillis();

        Session s = ((SessionIf)session).getSession();
        s.access(now);
        
        // Do we need to refresh the cookie?
        if (isUsingCookies() &&
            (s.isIdChanged() ||
             (getMaxCookieAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
            )
           )
        {
            Cookie cookie=getSessionCookie(session,_context.getContextPath(),secure);
            s.cookieSet();
            s.setIdChanged(false);
            return cookie;
        }
        
        return null;
    }

    /* ------------------------------------------------------------ */
    public void addEventListener(EventListener listener)
    {
        if (listener instanceof HttpSessionAttributeListener)
            _sessionAttributeListeners=LazyList.add(_sessionAttributeListeners,listener);
        if (listener instanceof HttpSessionListener)
            _sessionListeners=LazyList.add(_sessionListeners,listener);
    }

    /* ------------------------------------------------------------ */
    public void clearEventListeners()
    {
        _sessionAttributeListeners=null;
        _sessionListeners=null;
    }

    /* ------------------------------------------------------------ */
    public void complete(HttpSession session)
    {
        Session s = ((SessionIf)session).getSession();
        s.complete();
    }

    /* ------------------------------------------------------------ */
    public void doStart() throws Exception
    {
        _context=ContextHandler.getCurrentContext();
        _loader=Thread.currentThread().getContextClassLoader();

        if (_sessionIdManager==null)
        {
            Server server=getSessionHandler().getServer();
            synchronized (server)
            {
                _sessionIdManager=server.getSessionIdManager();
                if (_sessionIdManager==null)
                {
                    _sessionIdManager=new HashSessionIdManager();
                    server.setSessionIdManager(_sessionIdManager);
                }
            }
        }
        if (!_sessionIdManager.isStarted())
            _sessionIdManager.start();

        // Look for a session cookie name
        String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty);
        if (tmp!=null)
            _sessionCookie=tmp;
        
        tmp=_context.getInitParameter(SessionManager.__SessionURLProperty);
        if (tmp!=null)
        {
            _sessionURL=tmp;
            _sessionURLPrefix=";"+_sessionURL+"=";
        }

        // set up the max session cookie age if it isn't already
        if (_maxCookieAge==-1)
        {
            if (_context!=null)
            {
                String str=_context.getInitParameter(SessionManager.__MaxAgeProperty);
                if (str!=null)
                    _maxCookieAge=Integer.parseInt(str.trim());
            }
        }
        // set up the session domain if it isn't already
        if (_sessionDomain==null)
        {
            // only try the context initParams
            if (_context!=null)
                _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty);
        }

        // set up the sessionPath if it isn't already
        if (_sessionPath==null)
        {
            // only the context initParams
            if (_context!=null)
                _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
        }

        super.doStart();
    }

    /* ------------------------------------------------------------ */
    public void doStop() throws Exception
    {
        super.doStop();

        invalidateSessions();

        _loader=null;
    }

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

    /* ------------------------------------------------------------ */
    public HttpSession getHttpSession(String nodeId)
    {
        String cluster_id = getIdManager().getClusterId(nodeId);
        
        synchronized (this)
        {
            Session session = getSession(cluster_id);
            
            if (session!=null && !session.getNodeId().equals(nodeId))
                session.setIdChanged(true);
            return session;
        }
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /**
     * @return Returns the metaManager used for cross context session management
     */
    public SessionIdManager getIdManager()
    {
        return _sessionIdManager;
    }

    /* ------------------------------------------------------------ */
    public int getMaxCookieAge()
    {
        return _maxCookieAge;
    }

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

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

    /* ------------------------------------------------------------ */
    /**
     * @deprecated use {@link #getIdManager()}
     */
    public SessionIdManager getMetaManager()
    {
        return getIdManager();
    }

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

    /* ------------------------------------------------------------ */
    public int getRefreshCookieAge()
    {
        return _refreshCookieAge;
    }


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

    /* ------------------------------------------------------------ */
    public String getSessionCookie()
    {
        return _sessionCookie;
    }

    /* ------------------------------------------------------------ */
    public Cookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
    {
        if (isUsingCookies())
        {
            String id = getNodeId(session);
            Cookie cookie=getHttpOnly()?new HttpOnlyCookie(_sessionCookie,id):new Cookie(_sessionCookie,id);

            cookie.setPath((contextPath==null||contextPath.length()==0)?"/":contextPath);
            cookie.setMaxAge(getMaxCookieAge());
            cookie.setSecure(requestIsSecure&&getSecureCookies());

            // set up the overrides
            if (_sessionDomain!=null)
                cookie.setDomain(_sessionDomain);
            if (_sessionPath!=null)
                cookie.setPath(_sessionPath);

            return cookie;
        }
        return null;
    }

    public String getSessionDomain()
    {
        return _sessionDomain;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the sessionHandler.
     */
    public SessionHandler getSessionHandler()
    {
        return _sessionHandler;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @deprecated.  Need to review if it is needed.
     */
    public abstract Map getSessionMap();
    
    /* ------------------------------------------------------------ */
    public String getSessionPath()
    {
        return _sessionPath;
    }

    /* ------------------------------------------------------------ */
    public abstract int getSessions();

    /* ------------------------------------------------------------ */
    public String getSessionURL()
    {
        return _sessionURL;
    }

    /* ------------------------------------------------------------ */
    public String getSessionURLPrefix()
    {
        return _sessionURLPrefix;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the usingCookies.
     */
    public boolean isUsingCookies()
    {
        return _usingCookies;
    }

    /* ------------------------------------------------------------ */
    public boolean isValid(HttpSession session)
    {
        Session s = ((SessionIf)session).getSession();
        return s.isValid();
    }
    
    /* ------------------------------------------------------------ */
    public String getClusterId(HttpSession session)
    {
        Session s = ((SessionIf)session).getSession();
        return s.getClusterId();
    }
    
    /* ------------------------------------------------------------ */
    public String getNodeId(HttpSession session)
    {
        Session s = ((SessionIf)session).getSession();
        return s.getNodeId();
    }

    /* ------------------------------------------------------------ */
    /**
     * Create a new HttpSession for a request
     */
    public HttpSession newHttpSession(HttpServletRequest request)
    {
        Session session=newSession(request);
        session.setMaxInactiveInterval(_dftMaxIdleSecs);
        addSession(session,true);
        return session;
    }

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

    /* ------------------------------------------------------------ */
    public void resetStats()
    {
        _minSessions=getSessions();
        _maxSessions=getSessions();
    }

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

    /* ------------------------------------------------------------ */
    /**
     * @param metaManager The metaManager used for cross context session management.
     */
    public void setIdManager(SessionIdManager metaManager)
    {
        _sessionIdManager=metaManager;
    }

    /* ------------------------------------------------------------ */
    public void setMaxCookieAge(int maxCookieAgeInSeconds)
    {
        _maxCookieAge=maxCookieAgeInSeconds;
        
        if (_maxCookieAge>0 && _refreshCookieAge==0)
            _refreshCookieAge=_maxCookieAge/3;
            
    }

    /* ------------------------------------------------------------ */
    /**
     * @param seconds
     */
    public void setMaxInactiveInterval(int seconds)
    {
        _dftMaxIdleSecs=seconds;
    }

    /* ------------------------------------------------------------ */
    /**
     * @deprecated use {@link #setIdManager(SessionIdManager)}
     */
    public void setMetaManager(SessionIdManager metaManager)
    {
        setIdManager(metaManager);
    }

    /* ------------------------------------------------------------ */
    public void setRefreshCookieAge(int ageInSeconds)
    {
        _refreshCookieAge=ageInSeconds;
    }


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

    public void setSessionCookie(String cookieName)
    {
        _sessionCookie=cookieName;
    }

    public void setSessionDomain(String domain)
    {
        _sessionDomain=domain;
    }

    /* ------------------------------------------------------------ */
    /**
     * @param sessionHandler
     *            The sessionHandler to set.
     */
    public void setSessionHandler(SessionHandler sessionHandler)
    {
        _sessionHandler=sessionHandler;
    }

    /* ------------------------------------------------------------ */
    public void setSessionPath(String path)
    {
        _sessionPath=path;
    }

    /* ------------------------------------------------------------ */
    public void setSessionURL(String url)
    {
        _sessionURL=url;
    }
    /* ------------------------------------------------------------ */
    /**
     * @param usingCookies
     *            The usingCookies to set.
     */
    public void setUsingCookies(boolean usingCookies)
    {
        _usingCookies=usingCookies;
    }


    protected abstract void addSession(Session session);

    /* ------------------------------------------------------------ */
    /**
     * Add the session Registers the session with this manager and registers the
     * session ID with the sessionIDManager;
     */
    protected void addSession(Session session, boolean created)
    {
        synchronized (_sessionIdManager)
        {
            _sessionIdManager.addSession(session);
            synchronized (this)
            {
                addSession(session);
                if (getSessions()>this._maxSessions)
                    this._maxSessions=getSessions();
            }
        }

        if (!created)
        {
            session.didActivate();
        }
        else if (_sessionListeners!=null)
        {
            HttpSessionEvent event=new HttpSessionEvent(session);
            for (int i=0; i<LazyList.size(_sessionListeners); i++)
                ((HttpSessionListener)LazyList.get(_sessionListeners,i)).sessionCreated(event);
        }
    }
    
    /* ------------------------------------------------------------ */
    /**
     * Get a known existingsession
     * @param idInCluster The session ID in the cluster, stripped of any worker name.
     * @return A Session or null if none exists.
     */
    public abstract Session getSession(String idInCluster);

    protected abstract void invalidateSessions();

    
    /* ------------------------------------------------------------ */
    /**
     * Create a new session instance
     * @param request
     * @return
     */
    protected abstract Session newSession(HttpServletRequest request);
    


    /* ------------------------------------------------------------ */
    /**
     * @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false. 
     */
    public boolean isNodeIdInSessionId()
    {
        return _nodeIdInSessionId;
    }

    /* ------------------------------------------------------------ */
    /**
     * @param nodeIdInSessionId true if the cluster node id (worker id) will be returned as part of the session id by {@link HttpSession#getId()}. Default is false.
     */
    public void setNodeIdInSessionId(boolean nodeIdInSessionId)
    {
        _nodeIdInSessionId=nodeIdInSessionId;
    }

    /* ------------------------------------------------------------ */
    /** Remove session from manager 
     * @param session The session to remove
     * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
     * {@link SessionIdManager#invalidateAll(String)} should be called.
     */
    public void removeSession(HttpSession session, boolean invalidate)
    {
        Session s = ((SessionIf)session).getSession();
        removeSession(s,invalidate);
    }

    /* ------------------------------------------------------------ */
    /** Remove session from manager 
     * @param session The session to remove
     * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
     * {@link SessionIdManager#invalidateAll(String)} should be called.
     */
    public void removeSession(Session session, boolean invalidate)
    {
        // Remove session from context and global maps
        synchronized (_sessionIdManager)
        {
            boolean removed = false;
            
            synchronized (this)
            {
                //take this session out of the map of sessions for this context
                if (getSession(session.getClusterId()) != null)
                {
                    removed = true;
                    removeSession(session.getClusterId());
                }
            }   
            
            if (removed)
            {
                // Remove session from all context and global id maps
                _sessionIdManager.removeSession(session);
                if (invalidate)
                    _sessionIdManager.invalidateAll(session.getClusterId());
            }
        }
        
        if (invalidate && _sessionListeners!=null)
        {
            HttpSessionEvent event=new HttpSessionEvent(session);
            for (int i=LazyList.size(_sessionListeners); i-->0;)
                ((HttpSessionListener)LazyList.get(_sessionListeners,i)).sessionDestroyed(event);
        }
        if (!invalidate)
        {
            session.willPassivate();
        }
    }

    /* ------------------------------------------------------------ */
    protected abstract void removeSession(String idInCluster);
    
    /* ------------------------------------------------------------ */
    /**
     * Null returning implementation of HttpSessionContext
     * 
     * @author Greg Wilkins (gregw)
     */
    public static class NullSessionContext implements HttpSessionContext
    {
        /* ------------------------------------------------------------ */
        private NullSessionContext()
        {
        }

        /* ------------------------------------------------------------ */
        /**
         * @deprecated From HttpSessionContext
         */
        public Enumeration getIds()
        {
            return Collections.enumeration(Collections.EMPTY_LIST);
        }

        /* ------------------------------------------------------------ */
        /**
         * @deprecated From HttpSessionContext
         */
        public HttpSession getSession(String id)
        {
            return null;
        }
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /**
     * Interface that any session wrapper should implement so that
     * SessionManager may access the Jetty session implementation.
     *
     */
    public interface SessionIf extends HttpSession
    {
        public Session getSession();
    }
    
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /**
     * 
     * <p>
     * Implements {@link javax.servlet.HttpSession} from the {@link javax.servlet} package.   
     * </p>
     * @author gregw
     *
     */
    public abstract class Session implements SessionIf, Serializable
    {
        protected final String _clusterId;
        protected final String _nodeId;
        protected boolean _idChanged;
        protected final long _created;
        protected long _cookieSet;
        protected long _accessed;
        protected long _lastAccessed;
        protected boolean _invalid;
        protected boolean _doInvalidate;
        protected long _maxIdleMs=_dftMaxIdleSecs*1000;
        protected boolean _newSession;
        protected Map _values;
        protected int _requests;

        /* ------------------------------------------------------------- */
        protected Session(HttpServletRequest request)
        {
            _newSession=true;
            _created=System.currentTimeMillis();
            _clusterId=_sessionIdManager.newSessionId(request,_created);
            _nodeId=_sessionIdManager.getNodeId(_clusterId,request);
            _accessed=_created;
            _requests=1;
        }

        /* ------------------------------------------------------------- */
        protected Session(long created, String clusterId)
        {
            _created=created;
            _clusterId=clusterId;
            _nodeId=_sessionIdManager.getNodeId(_clusterId,null);
            _accessed=_created;
        }

        /* ------------------------------------------------------------- */
        public Session getSession()
        {
            return this;
        }
        
        /* ------------------------------------------------------------- */
        protected void initValues() 
        {
            _values=newAttributeMap();
        }

        /* ------------------------------------------------------------ */
        public synchronized Object getAttribute(String name)
        {
            if (_invalid)
                throw new IllegalStateException();

            if (null == _values)
                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 long getCookieSetTime() 
        {
            return _cookieSet;
        }

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

        /* ------------------------------------------------------------ */
        public String getId() throws IllegalStateException
        {
            return _nodeIdInSessionId?_nodeId:_clusterId;
        }

        /* ------------------------------------------------------------- */
        protected String getNodeId()
        {
            return _nodeId;
        }
        
        /* ------------------------------------------------------------- */
        protected String getClusterId()
        {
            return _clusterId;
        }

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

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

        /* ------------------------------------------------------------ */
        /*
         * @see javax.servlet.http.HttpSession#getServletContext()
         */
        public ServletContext getServletContext()
        {
            return _context;
        }

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

        /* ------------------------------------------------------------- */
        /**
         * @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);
        }

        /* ------------------------------------------------------------ */
        protected void access(long time)
        {
            synchronized(this)
            {
                _newSession=false;
                _lastAccessed=_accessed;
                _accessed=time;
                _requests++;
            }
        }

        /* ------------------------------------------------------------ */
        protected void complete()
        {
            synchronized(this)
            {
                _requests--;
                if (_doInvalidate && _requests<=0  )
                    doInvalidate();
            }
        }
        
        
        /* ------------------------------------------------------------- */
        protected void timeout() throws IllegalStateException
        {
            // remove session from context and invalidate other sessions with same ID.
            removeSession(this,true);

            // Notify listeners and unbind values
            synchronized (this)
            {
                if (_requests<=0)
                    doInvalidate();
                else
                    _doInvalidate=true;
            }
        }
        
        /* ------------------------------------------------------------- */
        public void invalidate() throws IllegalStateException
        {
            // remove session from context and invalidate other sessions with same ID.
            removeSession(this,true);
            doInvalidate();
        }
        
        /* ------------------------------------------------------------- */
        protected void doInvalidate() throws IllegalStateException
        {
            try
            {
                // Notify listeners and unbind values
                if (_invalid)
                    throw new IllegalStateException();

                while (_values!=null && _values.size()>0)
                {
                    ArrayList keys;
                    synchronized (this)
                    {
                        keys=new ArrayList(_values.keySet());
                    }

                    Iterator iter=keys.iterator();
                    while (iter.hasNext())
                    {
                        String key=(String)iter.next();

                        Object value;
                        synchronized (this)
                        {
                            value=_values.remove(key);
                        }
                        unbindValue(key,value);

                        if (_sessionAttributeListeners!=null)
                        {
                            HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,key,value);

                            for (int i=0; i<LazyList.size(_sessionAttributeListeners); i++)
                                ((HttpSessionAttributeListener)LazyList.get(_sessionAttributeListeners,i)).attributeRemoved(event);
                        }
                    }
                }
            }
            finally
            {
                // mark as invalid
                _invalid=true;
            }
        }

        /* ------------------------------------------------------------- */
        public boolean isIdChanged()
        {
            return _idChanged;
        }

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

        /* ------------------------------------------------------------- */
        /**
         * @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);
        }

        /* ------------------------------------------------------------ */
        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!=null)
                {
                    HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,name,old);

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

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

        /* ------------------------------------------------------------ */
        public synchronized void setAttribute(String name, Object value)
        {
            if (value==null)
            {
                removeAttribute(name);
                return;
            }

            if (_invalid)
                throw new IllegalStateException();
            if (_values==null)
                _values=newAttributeMap();
            Object oldValue=_values.put(name,value);

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

                if (_sessionAttributeListeners!=null)
                {
                    HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,name,oldValue==null?value:oldValue);

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

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

        /* ------------------------------------------------------------- */
        public void setIdChanged(boolean changed)
        {
            _idChanged=changed;
        }

        /* ------------------------------------------------------------- */
        public void setMaxInactiveInterval(int secs)
        {
            _maxIdleMs=(long)secs*1000;
        }

        /* ------------------------------------------------------------- */
        public String toString()
        {
            return this.getClass().getName()+":"+getId()+"@"+hashCode();
        }

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

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

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

        /* ------------------------------------------------------------- */
        protected void cookieSet()
        {
            _cookieSet=_accessed;
        }

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

        /* ------------------------------------------------------------- */
        protected synchronized void willPassivate() 
        {
            HttpSessionEvent event = new HttpSessionEvent(this);
            for (Iterator iter = _values.values().iterator(); iter.hasNext();) 
            {
                Object value = iter.next();
                if (value instanceof HttpSessionActivationListener) 
                {
                    HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
                    listener.sessionWillPassivate(event);
                }
            }
        }

        /* ------------------------------------------------------------- */
        protected synchronized void didActivate() 
        {
            HttpSessionEvent event = new HttpSessionEvent(this);
            for (Iterator iter = _values.values().iterator(); iter.hasNext();) 
            {
                Object value = iter.next();
                if (value instanceof HttpSessionActivationListener) 
                {
                    HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
                    listener.sessionDidActivate(event);
                }
            }
        }
    }
}
