]> WPIA git - gigi.git/blobdiff - lib/jetty/org/eclipse/jetty/server/session/AbstractSessionManager.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / server / session / AbstractSessionManager.java
diff --git a/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionManager.java b/lib/jetty/org/eclipse/jetty/server/session/AbstractSessionManager.java
new file mode 100644 (file)
index 0000000..1f966c5
--- /dev/null
@@ -0,0 +1,1052 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import static java.lang.Math.round;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+/* ------------------------------------------------------------ */
+/**
+ * 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 specialised version of the Session inner class that provides an attribute
+ * Map.
+ * <p>
+ */
+@SuppressWarnings("deprecation")
+@ManagedObject("Abstract Session Manager")
+public abstract class AbstractSessionManager extends ContainerLifeCycle implements SessionManager
+{
+    final static Logger __log = SessionHandler.LOG;
+
+    public Set<SessionTrackingMode> __defaultSessionTrackingModes =
+        Collections.unmodifiableSet(
+            new HashSet<SessionTrackingMode>(
+                    Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE,SessionTrackingMode.URL})));
+
+    
+
+    /* ------------------------------------------------------------ */
+    public final static int __distantFuture=60*60*24*7*52*20;
+
+    static final HttpSessionContext __nullSessionContext=new HttpSessionContext()
+    {
+        @Override
+        public HttpSession getSession(String sessionId)
+        {
+            return null;
+        }
+
+        @Override
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public Enumeration getIds()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+    };
+
+    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 SessionIdManager _sessionIdManager;
+    protected boolean _secureCookies=false;
+    protected boolean _secureRequestOnly=true;
+
+    protected final List<HttpSessionAttributeListener> _sessionAttributeListeners = new CopyOnWriteArrayList<HttpSessionAttributeListener>();
+    protected final List<HttpSessionListener> _sessionListeners= new CopyOnWriteArrayList<HttpSessionListener>();
+    protected final List<HttpSessionIdListener> _sessionIdListeners = new CopyOnWriteArrayList<HttpSessionIdListener>();
+
+    protected ClassLoader _loader;
+    protected ContextHandler.Context _context;
+    protected String _sessionCookie=__DefaultSessionCookie;
+    protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
+    protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"=";
+    protected String _sessionDomain;
+    protected String _sessionPath;
+    protected int _maxCookieAge=-1;
+    protected int _refreshCookieAge;
+    protected boolean _nodeIdInSessionId;
+    protected boolean _checkingRemoteSessionIdEncoding;
+    protected String _sessionComment;
+
+    public Set<SessionTrackingMode> _sessionTrackingModes;
+
+    private boolean _usingURLs;
+
+    protected final CounterStatistic _sessionsStats = new CounterStatistic();
+    protected final SampleStatistic _sessionTimeStats = new SampleStatistic();
+
+
+    /* ------------------------------------------------------------ */
+    public AbstractSessionManager()
+    {
+        setSessionTrackingModes(__defaultSessionTrackingModes);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ContextHandler.Context getContext()
+    {
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ContextHandler getContextHandler()
+    {
+        return _context.getContextHandler();
+    }
+
+    @ManagedAttribute("path of the session cookie, or null for default")
+    public String getSessionPath()
+    {
+        return _sessionPath;
+    }
+
+    @ManagedAttribute("if greater the zero, the time in seconds a session cookie will last for")
+    public int getMaxCookieAge()
+    {
+        return _maxCookieAge;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public HttpCookie access(HttpSession session,boolean secure)
+    {
+        long now=System.currentTimeMillis();
+
+        AbstractSession s = ((SessionIf)session).getSession();
+
+       if (s.access(now))
+       {
+            // Do we need to refresh the cookie?
+            if (isUsingCookies() &&
+                (s.isIdChanged() ||
+                (getSessionCookieConfig().getMaxAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
+                )
+               )
+            {
+                HttpCookie cookie=getSessionCookie(session,_context==null?"/":(_context.getContextPath()),secure);
+                s.cookieSet();
+                s.setIdChanged(false);
+                return cookie;
+            }
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void addEventListener(EventListener listener)
+    {
+        if (listener instanceof HttpSessionAttributeListener)
+            _sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
+        if (listener instanceof HttpSessionListener)
+            _sessionListeners.add((HttpSessionListener)listener);
+        if (listener instanceof HttpSessionIdListener)
+            _sessionIdListeners.add((HttpSessionIdListener)listener);
+        addBean(listener,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void clearEventListeners()
+    {
+        for (EventListener e :getBeans(EventListener.class))
+            removeBean(e);
+        _sessionAttributeListeners.clear();
+        _sessionListeners.clear();
+        _sessionIdListeners.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void complete(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        s.complete();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStart() throws Exception
+    {
+        _context=ContextHandler.getCurrentContext();
+        _loader=Thread.currentThread().getContextClassLoader();
+
+        if (_sessionIdManager==null)
+        {
+            final Server server=getSessionHandler().getServer();
+            synchronized (server)
+            {
+                _sessionIdManager=server.getSessionIdManager();
+                if (_sessionIdManager==null)
+                {
+                    //create a default SessionIdManager and set it as the shared
+                    //SessionIdManager for the Server, being careful NOT to use
+                    //the webapp context's classloader, otherwise if the context
+                    //is stopped, the classloader is leaked.
+                    ClassLoader serverLoader = server.getClass().getClassLoader();
+                    try
+                    {
+                        Thread.currentThread().setContextClassLoader(serverLoader);
+                        _sessionIdManager=new HashSessionIdManager();
+                        server.setSessionIdManager(_sessionIdManager);
+                        server.manage(_sessionIdManager);
+                        _sessionIdManager.start();
+                    }
+                    finally
+                    {
+                        Thread.currentThread().setContextClassLoader(_loader);
+                    }
+                }
+
+                // server session id is never managed by this manager
+                addBean(_sessionIdManager,false);
+            }
+        }
+        
+
+        // Look for a session cookie name
+        if (_context!=null)
+        {
+            String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty);
+            if (tmp!=null)
+                _sessionCookie=tmp;
+
+            tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty);
+            if (tmp!=null)
+                setSessionIdPathParameterName(tmp);
+
+            // set up the max session cookie age if it isn't already
+            if (_maxCookieAge==-1)
+            {
+                tmp=_context.getInitParameter(SessionManager.__MaxAgeProperty);
+                if (tmp!=null)
+                    _maxCookieAge=Integer.parseInt(tmp.trim());
+            }
+
+            // set up the session domain if it isn't already
+            if (_sessionDomain==null)
+                _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty);
+
+            // set up the sessionPath if it isn't already
+            if (_sessionPath==null)
+                _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
+
+            tmp=_context.getInitParameter(SessionManager.__CheckRemoteSessionEncoding);
+            if (tmp!=null)
+                _checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
+        }
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStop() throws Exception
+    {
+        super.doStop();
+
+        shutdownSessions();
+
+        _loader=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the httpOnly.
+     */
+    @Override
+    @ManagedAttribute("true if cookies use the http only flag")
+    public boolean getHttpOnly()
+    {
+        return _httpOnly;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public HttpSession getHttpSession(String nodeId)
+    {
+        String cluster_id = getSessionIdManager().getClusterId(nodeId);
+
+        AbstractSession session = getSession(cluster_id);
+        if (session!=null && !session.getNodeId().equals(nodeId))
+            session.setIdChanged(true);
+        return session;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the SessionIdManager used for cross context session management
+     */
+    @Override
+    @ManagedAttribute("Session ID Manager")
+    public SessionIdManager getSessionIdManager()
+    {
+        return _sessionIdManager;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return seconds
+     */
+    @Override
+    @ManagedAttribute("defailt maximum time a session may be idle for (in s)")
+    public int getMaxInactiveInterval()
+    {
+        return _dftMaxIdleSecs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return maximum number of sessions
+     */
+    @ManagedAttribute("maximum number of simultaneous sessions")
+    public int getSessionsMax()
+    {
+        return (int)_sessionsStats.getMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return total number of sessions
+     */
+    @ManagedAttribute("total number of sessions")
+    public int getSessionsTotal()
+    {
+        return (int)_sessionsStats.getTotal();
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("time before a session cookie is re-set (in s)")
+    public int getRefreshCookieAge()
+    {
+        return _refreshCookieAge;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return same as SessionCookieConfig.getSecure(). If true, session
+     * cookies are ALWAYS marked as secure. If false, a session cookie is
+     * ONLY marked as secure if _secureRequestOnly == true and it is a HTTPS request.
+     */
+    @ManagedAttribute("if true, secure cookie flag is set on session cookies")
+    public boolean getSecureCookies()
+    {
+        return _secureCookies;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if session cookie is to be marked as secure only on HTTPS requests
+     */
+    public boolean isSecureRequestOnly()
+    {
+        return _secureRequestOnly;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * HTTPS request. Can be overridden by setting SessionCookieConfig.setSecure(true),
+     * in which case the session cookie will be marked as secure on both HTTPS and HTTP.
+     */
+    public void setSecureRequestOnly(boolean secureRequestOnly)
+    {
+        _secureRequestOnly = secureRequestOnly;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("the set session cookie")
+    public String getSessionCookie()
+    {
+        return _sessionCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A sessioncookie is marked as secure IFF any of the following conditions are true:
+     * <ol>
+     * <li>SessionCookieConfig.setSecure == true</li>
+     * <li>SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS</li>
+     * </ol>
+     * According to SessionCookieConfig javadoc, case 1 can be used when:
+     * "... even though the request that initiated the session came over HTTP,
+     * is to support a topology where the web container is front-ended by an
+     * SSL offloading load balancer. In this case, the traffic between the client
+     * and the load balancer will be over HTTPS, whereas the traffic between the
+     * load balancer and the web container will be over HTTP."
+     *
+     * For case 2, you can use _secureRequestOnly to determine if you want the
+     * Servlet Spec 3.0  default behaviour when SessionCookieConfig.setSecure==false,
+     * which is:
+     * "they shall be marked as secure only if the request that initiated the
+     * corresponding session was also secure"
+     *
+     * The default for _secureRequestOnly is true, which gives the above behaviour. If
+     * you set it to false, then a session cookie is NEVER marked as secure, even if
+     * the initiating request was secure.
+     *
+     * @see org.eclipse.jetty.server.SessionManager#getSessionCookie(javax.servlet.http.HttpSession, java.lang.String, boolean)
+     */
+    @Override
+    public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
+    {
+        if (isUsingCookies())
+        {
+            String sessionPath = (_cookieConfig.getPath()==null) ? contextPath : _cookieConfig.getPath();
+            sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
+            String id = getNodeId(session);
+            HttpCookie cookie = null;
+            if (_sessionComment == null)
+            {
+                cookie = new HttpCookie(
+                                        _cookieConfig.getName(),
+                                        id,
+                                        _cookieConfig.getDomain(),
+                                        sessionPath,
+                                        _cookieConfig.getMaxAge(),
+                                        _cookieConfig.isHttpOnly(),
+                                        _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure));
+            }
+            else
+            {
+                cookie = new HttpCookie(
+                                        _cookieConfig.getName(),
+                                        id,
+                                        _cookieConfig.getDomain(),
+                                        sessionPath,
+                                        _cookieConfig.getMaxAge(),
+                                        _cookieConfig.isHttpOnly(),
+                                        _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
+                                        _sessionComment,
+                                        1);
+            }
+
+            return cookie;
+        }
+        return null;
+    }
+
+    @ManagedAttribute("domain of the session cookie, or null for the default")
+    public String getSessionDomain()
+    {
+        return _sessionDomain;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionHandler.
+     */
+    public SessionHandler getSessionHandler()
+    {
+        return _sessionHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    @ManagedAttribute("number of currently active sessions")
+    public int getSessions()
+    {
+        return (int)_sessionsStats.getCurrent();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    @ManagedAttribute("name of use for URL session tracking")
+    public String getSessionIdPathParameterName()
+    {
+        return _sessionIdPathParameterName;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getSessionIdPathParameterNamePrefix()
+    {
+        return _sessionIdPathParameterNamePrefix;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the usingCookies.
+     */
+    @Override
+    public boolean isUsingCookies()
+    {
+        return _usingCookies;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isValid(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.isValid();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getClusterId(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.getClusterId();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getNodeId(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.getNodeId();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new HttpSession for a request
+     */
+    @Override
+    public HttpSession newHttpSession(HttpServletRequest request)
+    {
+        AbstractSession session=newSession(request);
+        session.setMaxInactiveInterval(_dftMaxIdleSecs);
+        addSession(session,true);
+        return session;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void removeEventListener(EventListener listener)
+    {
+        if (listener instanceof HttpSessionAttributeListener)
+            _sessionAttributeListeners.remove(listener);
+        if (listener instanceof HttpSessionListener)
+            _sessionListeners.remove(listener);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Reset statistics values
+     */
+    @ManagedOperation(value="reset statistics", impact="ACTION")
+    public void statsReset()
+    {
+        _sessionsStats.reset(getSessions());
+        _sessionTimeStats.reset();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param httpOnly
+     *            The httpOnly to set.
+     */
+    public void setHttpOnly(boolean httpOnly)
+    {
+        _httpOnly=httpOnly;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param metaManager The metaManager used for cross context session management.
+     */
+    @Override
+    public void setSessionIdManager(SessionIdManager metaManager)
+    {
+        updateBean(_sessionIdManager, metaManager);
+        _sessionIdManager=metaManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param seconds
+     */
+    @Override
+    public void setMaxInactiveInterval(int seconds)
+    {
+        _dftMaxIdleSecs=seconds;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRefreshCookieAge(int ageInSeconds)
+    {
+        _refreshCookieAge=ageInSeconds;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setSessionCookie(String cookieName)
+    {
+        _sessionCookie=cookieName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionHandler
+     *            The sessionHandler to set.
+     */
+    @Override
+    public void setSessionHandler(SessionHandler sessionHandler)
+    {
+        _sessionHandler=sessionHandler;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setSessionIdPathParameterName(String param)
+    {
+        _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
+        _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
+    }
+    /* ------------------------------------------------------------ */
+    /**
+     * @param usingCookies
+     *            The usingCookies to set.
+     */
+    public void setUsingCookies(boolean usingCookies)
+    {
+        _usingCookies=usingCookies;
+    }
+
+
+    protected abstract void addSession(AbstractSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add the session Registers the session with this manager and registers the
+     * session ID with the sessionIDManager;
+     */
+    protected void addSession(AbstractSession session, boolean created)
+    {
+        synchronized (_sessionIdManager)
+        {
+            _sessionIdManager.addSession(session);
+            addSession(session);
+        }
+
+        if (created)
+        {
+            _sessionsStats.increment();
+            if (_sessionListeners!=null)
+            {
+                HttpSessionEvent event=new HttpSessionEvent(session);
+                for (HttpSessionListener listener : _sessionListeners)
+                    listener.sessionCreated(event);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a known existing session
+     * @param idInCluster The session ID in the cluster, stripped of any worker name.
+     * @return A Session or null if none exists.
+     */
+    public abstract AbstractSession getSession(String idInCluster);
+
+    /**
+     * Prepare sessions for session manager shutdown
+     * 
+     * @throws Exception
+     */
+    protected abstract void shutdownSessions() throws Exception;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new session instance
+     * @param request
+     * @return the new session
+     */
+    protected abstract AbstractSession 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)
+    {
+        AbstractSession 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 boolean removeSession(AbstractSession session, boolean invalidate)
+    {
+        // Remove session from context and global maps
+        boolean removed = removeSession(session.getClusterId());
+
+        if (removed)
+        {
+            _sessionsStats.decrement();
+            _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0));
+
+            // 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 = _sessionListeners.size()-1; i>=0; i--)
+                {
+                    _sessionListeners.get(i).sessionDestroyed(event);
+                }
+            }
+        }
+        
+        return removed;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean removeSession(String idInCluster);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return maximum amount of time session remained valid
+     */
+    @ManagedAttribute("maximum amount of time sessions have remained active (in s)")
+    public long getSessionTimeMax()
+    {
+        return _sessionTimeStats.getMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+    {
+        return __defaultSessionTrackingModes;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+    {
+        return Collections.unmodifiableSet(_sessionTrackingModes);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+    {
+        _sessionTrackingModes=new HashSet<SessionTrackingMode>(sessionTrackingModes);
+        _usingCookies=_sessionTrackingModes.contains(SessionTrackingMode.COOKIE);
+        _usingURLs=_sessionTrackingModes.contains(SessionTrackingMode.URL);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isUsingURLs()
+    {
+        return _usingURLs;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public SessionCookieConfig getSessionCookieConfig()
+    {
+        return _cookieConfig;
+    }
+
+    /* ------------------------------------------------------------ */
+    private SessionCookieConfig _cookieConfig =
+        new CookieConfig();
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return total amount of time all sessions remained valid
+     */
+    @ManagedAttribute("total time sessions have remained valid")
+    public long getSessionTimeTotal()
+    {
+        return _sessionTimeStats.getTotal();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return mean amount of time session remained valid
+     */
+    @ManagedAttribute("mean time sessions remain valid (in s)")
+    public double getSessionTimeMean()
+    {
+        return _sessionTimeStats.getMean();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return standard deviation of amount of time session remained valid
+     */
+    @ManagedAttribute("standard deviation a session remained valid (in s)")
+    public double getSessionTimeStdDev()
+    {
+        return _sessionTimeStats.getStdDev();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding()
+     */
+    @Override
+    @ManagedAttribute("check remote session id encoding")
+    public boolean isCheckingRemoteSessionIdEncoding()
+    {
+        return _checkingRemoteSessionIdEncoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean)
+     */
+    @Override
+    public void setCheckingRemoteSessionIdEncoding(boolean remote)
+    {
+        _checkingRemoteSessionIdEncoding=remote;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Tell the HttpSessionIdListeners the id changed.
+     * NOTE: this method must be called LAST in subclass overrides, after the session has been updated
+     * with the new id.
+     * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+     */
+    @Override
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+    {
+        if (!_sessionIdListeners.isEmpty())
+        {
+            AbstractSession session = getSession(newClusterId);
+            HttpSessionEvent event = new HttpSessionEvent(session);
+            for (HttpSessionIdListener l:_sessionIdListeners)
+            {
+                l.sessionIdChanged(event, oldClusterId);
+            }
+        }
+
+    }
+
+    /**
+     * CookieConfig
+     * 
+     * Implementation of the javax.servlet.SessionCookieConfig.
+     */
+    public final class CookieConfig implements SessionCookieConfig
+    {
+        @Override
+        public String getComment()
+        {
+            return _sessionComment;
+        }
+
+        @Override
+        public String getDomain()
+        {
+            return _sessionDomain;
+        }
+
+        @Override
+        public int getMaxAge()
+        {
+            return _maxCookieAge;
+        }
+
+        @Override
+        public String getName()
+        {
+            return _sessionCookie;
+        }
+
+        @Override
+        public String getPath()
+        {
+            return _sessionPath;
+        }
+
+        @Override
+        public boolean isHttpOnly()
+        {
+            return _httpOnly;
+        }
+
+        @Override
+        public boolean isSecure()
+        {
+            return _secureCookies;
+        }
+
+        @Override
+        public void setComment(String comment)
+        {  
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionComment = comment;
+        }
+
+        @Override
+        public void setDomain(String domain)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionDomain=domain;
+        }
+
+        @Override
+        public void setHttpOnly(boolean httpOnly)
+        {   
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _httpOnly=httpOnly;
+        }
+
+        @Override
+        public void setMaxAge(int maxAge)
+        {               
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _maxCookieAge=maxAge;
+        }
+
+        @Override
+        public void setName(String name)
+        {  
+                if (_context != null && _context.getContextHandler().isAvailable())
+                    throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _sessionCookie=name;
+        }
+
+        @Override
+        public void setPath(String path)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started"); 
+            _sessionPath=path;
+        }
+
+        @Override
+        public void setSecure(boolean secure)
+        {
+            if (_context != null && _context.getContextHandler().isAvailable())
+                throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+            _secureCookies=secure;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Interface that any session wrapper should implement so that
+     * SessionManager may access the Jetty session implementation.
+     *
+     */
+    public interface SessionIf extends HttpSession
+    {
+        public AbstractSession getSession();
+    }
+
+    public void doSessionAttributeListeners(AbstractSession session, String name, Object old, Object value)
+    {
+        if (!_sessionAttributeListeners.isEmpty())
+        {
+            HttpSessionBindingEvent event=new HttpSessionBindingEvent(session,name,old==null?value:old);
+
+            for (HttpSessionAttributeListener l : _sessionAttributeListeners)
+            {
+                if (old==null)
+                    l.attributeAdded(event);
+                else if (value==null)
+                    l.attributeRemoved(event);
+                else
+                    l.attributeReplaced(event);
+            }
+        }
+    }
+
+    @Override
+    @Deprecated
+    public SessionIdManager getMetaManager()
+    {
+        throw new UnsupportedOperationException();
+    }
+}