View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server.session;
20  
21  import static java.lang.Math.round;
22  
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.Enumeration;
26  import java.util.EventListener;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.concurrent.CopyOnWriteArrayList;
31  
32  import javax.servlet.SessionCookieConfig;
33  import javax.servlet.SessionTrackingMode;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpSession;
36  import javax.servlet.http.HttpSessionAttributeListener;
37  import javax.servlet.http.HttpSessionBindingEvent;
38  import javax.servlet.http.HttpSessionContext;
39  import javax.servlet.http.HttpSessionEvent;
40  import javax.servlet.http.HttpSessionIdListener;
41  import javax.servlet.http.HttpSessionListener;
42  
43  import org.eclipse.jetty.http.HttpCookie;
44  import org.eclipse.jetty.server.Server;
45  import org.eclipse.jetty.server.SessionIdManager;
46  import org.eclipse.jetty.server.SessionManager;
47  import org.eclipse.jetty.server.handler.ContextHandler;
48  import org.eclipse.jetty.util.annotation.ManagedAttribute;
49  import org.eclipse.jetty.util.annotation.ManagedObject;
50  import org.eclipse.jetty.util.annotation.ManagedOperation;
51  import org.eclipse.jetty.util.component.ContainerLifeCycle;
52  import org.eclipse.jetty.util.log.Logger;
53  import org.eclipse.jetty.util.statistic.CounterStatistic;
54  import org.eclipse.jetty.util.statistic.SampleStatistic;
55  
56  /**
57   * An Abstract implementation of SessionManager.
58   * <p>
59   * The partial implementation of SessionManager interface provides the majority of the handling required to implement a
60   * SessionManager. Concrete implementations of SessionManager based on AbstractSessionManager need only implement the
61   * newSession method to return a specialized version of the Session inner class that provides an attribute Map.
62   */
63  @SuppressWarnings("deprecation")
64  @ManagedObject("Abstract Session Manager")
65  public abstract class AbstractSessionManager extends ContainerLifeCycle implements SessionManager
66  {
67      final static Logger __log = SessionHandler.LOG;
68  
69      public Set<SessionTrackingMode> __defaultSessionTrackingModes =
70          Collections.unmodifiableSet(
71              new HashSet<SessionTrackingMode>(
72                      Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE,SessionTrackingMode.URL})));
73  
74      
75  
76      /* ------------------------------------------------------------ */
77      public final static int __distantFuture=60*60*24*7*52*20;
78  
79      static final HttpSessionContext __nullSessionContext=new HttpSessionContext()
80      {
81          @Override
82          public HttpSession getSession(String sessionId)
83          {
84              return null;
85          }
86  
87          @Override
88          @SuppressWarnings({ "rawtypes", "unchecked" })
89          public Enumeration getIds()
90          {
91              return Collections.enumeration(Collections.EMPTY_LIST);
92          }
93      };
94  
95      private boolean _usingCookies=true;
96  
97      /* ------------------------------------------------------------ */
98      // Setting of max inactive interval for new sessions
99      // -1 means no timeout
100     protected int _dftMaxIdleSecs=-1;
101     protected SessionHandler _sessionHandler;
102     protected boolean _httpOnly=false;
103     protected SessionIdManager _sessionIdManager;
104     protected boolean _secureCookies=false;
105     protected boolean _secureRequestOnly=true;
106 
107     protected final List<HttpSessionAttributeListener> _sessionAttributeListeners = new CopyOnWriteArrayList<HttpSessionAttributeListener>();
108     protected final List<HttpSessionListener> _sessionListeners= new CopyOnWriteArrayList<HttpSessionListener>();
109     protected final List<HttpSessionIdListener> _sessionIdListeners = new CopyOnWriteArrayList<HttpSessionIdListener>();
110 
111     protected ClassLoader _loader;
112     protected ContextHandler.Context _context;
113     protected String _sessionCookie=__DefaultSessionCookie;
114     protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
115     protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"=";
116     protected String _sessionDomain;
117     protected String _sessionPath;
118     protected int _maxCookieAge=-1;
119     protected int _refreshCookieAge;
120     protected boolean _nodeIdInSessionId;
121     protected boolean _checkingRemoteSessionIdEncoding;
122     protected String _sessionComment;
123 
124     public Set<SessionTrackingMode> _sessionTrackingModes;
125 
126     private boolean _usingURLs;
127 
128     protected final CounterStatistic _sessionsStats = new CounterStatistic();
129     protected final SampleStatistic _sessionTimeStats = new SampleStatistic();
130 
131 
132     /* ------------------------------------------------------------ */
133     public AbstractSessionManager()
134     {
135         setSessionTrackingModes(__defaultSessionTrackingModes);
136     }
137 
138     /* ------------------------------------------------------------ */
139     public ContextHandler.Context getContext()
140     {
141         return _context;
142     }
143 
144     /* ------------------------------------------------------------ */
145     public ContextHandler getContextHandler()
146     {
147         return _context.getContextHandler();
148     }
149 
150     @ManagedAttribute("path of the session cookie, or null for default")
151     public String getSessionPath()
152     {
153         return _sessionPath;
154     }
155 
156     @ManagedAttribute("if greater the zero, the time in seconds a session cookie will last for")
157     public int getMaxCookieAge()
158     {
159         return _maxCookieAge;
160     }
161 
162     /* ------------------------------------------------------------ */
163     @Override
164     public HttpCookie access(HttpSession session,boolean secure)
165     {
166         long now=System.currentTimeMillis();
167 
168         AbstractSession s = ((SessionIf)session).getSession();
169 
170        if (s.access(now))
171        {
172             // Do we need to refresh the cookie?
173             if (isUsingCookies() &&
174                 (s.isIdChanged() ||
175                 (getSessionCookieConfig().getMaxAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
176                 )
177                )
178             {
179                 HttpCookie cookie=getSessionCookie(session,_context==null?"/":(_context.getContextPath()),secure);
180                 s.cookieSet();
181                 s.setIdChanged(false);
182                 return cookie;
183             }
184         }
185         return null;
186     }
187 
188     /* ------------------------------------------------------------ */
189     @Override
190     public void addEventListener(EventListener listener)
191     {
192         if (listener instanceof HttpSessionAttributeListener)
193             _sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
194         if (listener instanceof HttpSessionListener)
195             _sessionListeners.add((HttpSessionListener)listener);
196         if (listener instanceof HttpSessionIdListener)
197             _sessionIdListeners.add((HttpSessionIdListener)listener);
198         addBean(listener,false);
199     }
200 
201     /* ------------------------------------------------------------ */
202     @Override
203     public void clearEventListeners()
204     {
205         for (EventListener e :getBeans(EventListener.class))
206             removeBean(e);
207         _sessionAttributeListeners.clear();
208         _sessionListeners.clear();
209         _sessionIdListeners.clear();
210     }
211 
212     /* ------------------------------------------------------------ */
213     @Override
214     public void complete(HttpSession session)
215     {
216         AbstractSession s = ((SessionIf)session).getSession();
217         s.complete();
218     }
219 
220     /* ------------------------------------------------------------ */
221     @Override
222     public void doStart() throws Exception
223     {
224         _context=ContextHandler.getCurrentContext();
225         _loader=Thread.currentThread().getContextClassLoader();
226 
227         final Server server=getSessionHandler().getServer();
228         synchronized (server)
229         {
230             if (_sessionIdManager==null)
231             {
232                 _sessionIdManager=server.getSessionIdManager();
233                 if (_sessionIdManager==null)
234                 {
235                     //create a default SessionIdManager and set it as the shared
236                     //SessionIdManager for the Server, being careful NOT to use
237                     //the webapp context's classloader, otherwise if the context
238                     //is stopped, the classloader is leaked.
239                     ClassLoader serverLoader = server.getClass().getClassLoader();
240                     try
241                     {
242                         Thread.currentThread().setContextClassLoader(serverLoader);
243                         _sessionIdManager=new HashSessionIdManager();
244                         server.setSessionIdManager(_sessionIdManager);
245                         server.manage(_sessionIdManager);
246                         _sessionIdManager.start();
247                     }
248                     finally
249                     {
250                         Thread.currentThread().setContextClassLoader(_loader);
251                     }
252                 }
253 
254                 // server session id is never managed by this manager
255                 addBean(_sessionIdManager,false);
256             }
257         }
258         
259 
260         // Look for a session cookie name
261         if (_context!=null)
262         {
263             String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty);
264             if (tmp!=null)
265                 _sessionCookie=tmp;
266 
267             tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty);
268             if (tmp!=null)
269                 setSessionIdPathParameterName(tmp);
270 
271             // set up the max session cookie age if it isn't already
272             if (_maxCookieAge==-1)
273             {
274                 tmp=_context.getInitParameter(SessionManager.__MaxAgeProperty);
275                 if (tmp!=null)
276                     _maxCookieAge=Integer.parseInt(tmp.trim());
277             }
278 
279             // set up the session domain if it isn't already
280             if (_sessionDomain==null)
281                 _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty);
282 
283             // set up the sessionPath if it isn't already
284             if (_sessionPath==null)
285                 _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
286 
287             tmp=_context.getInitParameter(SessionManager.__CheckRemoteSessionEncoding);
288             if (tmp!=null)
289                 _checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
290         }
291 
292         super.doStart();
293     }
294 
295     /* ------------------------------------------------------------ */
296     @Override
297     public void doStop() throws Exception
298     {
299         super.doStop();
300 
301         shutdownSessions();
302 
303         _loader=null;
304     }
305 
306     /* ------------------------------------------------------------ */
307     /**
308      * @return Returns the httpOnly.
309      */
310     @Override
311     @ManagedAttribute("true if cookies use the http only flag")
312     public boolean getHttpOnly()
313     {
314         return _httpOnly;
315     }
316 
317     /* ------------------------------------------------------------ */
318     @Override
319     public HttpSession getHttpSession(String nodeId)
320     {
321         String cluster_id = getSessionIdManager().getClusterId(nodeId);
322 
323         AbstractSession session = getSession(cluster_id);
324         if (session!=null && !session.getNodeId().equals(nodeId))
325             session.setIdChanged(true);
326         return session;
327     }
328 
329     /* ------------------------------------------------------------ */
330     /**
331      * @return Returns the SessionIdManager used for cross context session management
332      */
333     @Override
334     @ManagedAttribute("Session ID Manager")
335     public SessionIdManager getSessionIdManager()
336     {
337         return _sessionIdManager;
338     }
339 
340 
341     /* ------------------------------------------------------------ */
342     /**
343      * @return seconds
344      */
345     @Override
346     @ManagedAttribute("defailt maximum time a session may be idle for (in s)")
347     public int getMaxInactiveInterval()
348     {
349         return _dftMaxIdleSecs;
350     }
351 
352     /* ------------------------------------------------------------ */
353     /**
354      * @return maximum number of sessions
355      */
356     @ManagedAttribute("maximum number of simultaneous sessions")
357     public int getSessionsMax()
358     {
359         return (int)_sessionsStats.getMax();
360     }
361 
362     /* ------------------------------------------------------------ */
363     /**
364      * @return total number of sessions
365      */
366     @ManagedAttribute("total number of sessions")
367     public int getSessionsTotal()
368     {
369         return (int)_sessionsStats.getTotal();
370     }
371 
372     /* ------------------------------------------------------------ */
373     @ManagedAttribute("time before a session cookie is re-set (in s)")
374     public int getRefreshCookieAge()
375     {
376         return _refreshCookieAge;
377     }
378 
379     /* ------------------------------------------------------------ */
380     /**
381      * @return same as SessionCookieConfig.getSecure(). If true, session
382      * cookies are ALWAYS marked as secure. If false, a session cookie is
383      * ONLY marked as secure if _secureRequestOnly == true and it is a HTTPS request.
384      */
385     @ManagedAttribute("if true, secure cookie flag is set on session cookies")
386     public boolean getSecureCookies()
387     {
388         return _secureCookies;
389     }
390 
391     /* ------------------------------------------------------------ */
392     /**
393      * @return true if session cookie is to be marked as secure only on HTTPS requests
394      */
395     public boolean isSecureRequestOnly()
396     {
397         return _secureRequestOnly;
398     }
399 
400 
401     /* ------------------------------------------------------------ */
402     /**
403      * HTTPS request. Can be overridden by setting SessionCookieConfig.setSecure(true),
404      * in which case the session cookie will be marked as secure on both HTTPS and HTTP.
405      * @param secureRequestOnly true to set Session Cookie Config as secure
406      */
407     public void setSecureRequestOnly(boolean secureRequestOnly)
408     {
409         _secureRequestOnly = secureRequestOnly;
410     }
411 
412     /* ------------------------------------------------------------ */
413     @ManagedAttribute("the set session cookie")
414     public String getSessionCookie()
415     {
416         return _sessionCookie;
417     }
418 
419     /* ------------------------------------------------------------ */
420     /**
421      * A sessioncookie is marked as secure IFF any of the following conditions are true:
422      * <ol>
423      * <li>SessionCookieConfig.setSecure == true</li>
424      * <li>SessionCookieConfig.setSecure == false &amp;&amp; _secureRequestOnly==true &amp;&amp; request is HTTPS</li>
425      * </ol>
426      * According to SessionCookieConfig javadoc, case 1 can be used when:
427      * "... even though the request that initiated the session came over HTTP,
428      * is to support a topology where the web container is front-ended by an
429      * SSL offloading load balancer. In this case, the traffic between the client
430      * and the load balancer will be over HTTPS, whereas the traffic between the
431      * load balancer and the web container will be over HTTP."
432      * <p>
433      * For case 2, you can use _secureRequestOnly to determine if you want the
434      * Servlet Spec 3.0  default behavior when SessionCookieConfig.setSecure==false,
435      * which is:
436      * <cite>
437      * "they shall be marked as secure only if the request that initiated the
438      * corresponding session was also secure"
439      * </cite>
440      * <p>
441      * The default for _secureRequestOnly is true, which gives the above behavior. If
442      * you set it to false, then a session cookie is NEVER marked as secure, even if
443      * the initiating request was secure.
444      *
445      * @see org.eclipse.jetty.server.SessionManager#getSessionCookie(javax.servlet.http.HttpSession, java.lang.String, boolean)
446      */
447     @Override
448     public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
449     {
450         if (isUsingCookies())
451         {
452             String sessionPath = (_cookieConfig.getPath()==null) ? contextPath : _cookieConfig.getPath();
453             sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
454             String id = getNodeId(session);
455             HttpCookie cookie = null;
456             if (_sessionComment == null)
457             {
458                 cookie = new HttpCookie(
459                                         _cookieConfig.getName(),
460                                         id,
461                                         _cookieConfig.getDomain(),
462                                         sessionPath,
463                                         _cookieConfig.getMaxAge(),
464                                         _cookieConfig.isHttpOnly(),
465                                         _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure));
466             }
467             else
468             {
469                 cookie = new HttpCookie(
470                                         _cookieConfig.getName(),
471                                         id,
472                                         _cookieConfig.getDomain(),
473                                         sessionPath,
474                                         _cookieConfig.getMaxAge(),
475                                         _cookieConfig.isHttpOnly(),
476                                         _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
477                                         _sessionComment,
478                                         1);
479             }
480 
481             return cookie;
482         }
483         return null;
484     }
485 
486     @ManagedAttribute("domain of the session cookie, or null for the default")
487     public String getSessionDomain()
488     {
489         return _sessionDomain;
490     }
491 
492     /* ------------------------------------------------------------ */
493     /**
494      * @return Returns the sessionHandler.
495      */
496     public SessionHandler getSessionHandler()
497     {
498         return _sessionHandler;
499     }
500 
501     /* ------------------------------------------------------------ */
502     @ManagedAttribute("number of currently active sessions")
503     public int getSessions()
504     {
505         return (int)_sessionsStats.getCurrent();
506     }
507 
508     /* ------------------------------------------------------------ */
509     @Override
510     @ManagedAttribute("name of use for URL session tracking")
511     public String getSessionIdPathParameterName()
512     {
513         return _sessionIdPathParameterName;
514     }
515 
516     /* ------------------------------------------------------------ */
517     @Override
518     public String getSessionIdPathParameterNamePrefix()
519     {
520         return _sessionIdPathParameterNamePrefix;
521     }
522 
523     /* ------------------------------------------------------------ */
524     /**
525      * @return Returns the usingCookies.
526      */
527     @Override
528     public boolean isUsingCookies()
529     {
530         return _usingCookies;
531     }
532 
533     /* ------------------------------------------------------------ */
534     @Override
535     public boolean isValid(HttpSession session)
536     {
537         AbstractSession s = ((SessionIf)session).getSession();
538         return s.isValid();
539     }
540 
541     /* ------------------------------------------------------------ */
542     @Override
543     public String getClusterId(HttpSession session)
544     {
545         AbstractSession s = ((SessionIf)session).getSession();
546         return s.getClusterId();
547     }
548 
549     /* ------------------------------------------------------------ */
550     @Override
551     public String getNodeId(HttpSession session)
552     {
553         AbstractSession s = ((SessionIf)session).getSession();
554         return s.getNodeId();
555     }
556 
557     /* ------------------------------------------------------------ */
558     /**
559      * Create a new HttpSession for a request
560      */
561     @Override
562     public HttpSession newHttpSession(HttpServletRequest request)
563     {
564         AbstractSession session=newSession(request);
565         session.setMaxInactiveInterval(_dftMaxIdleSecs);
566         if (request.isSecure())
567             session.setAttribute(AbstractSession.SESSION_CREATED_SECURE, Boolean.TRUE);
568         addSession(session,true);
569         return session;
570     }
571 
572     /* ------------------------------------------------------------ */
573     @Override
574     public void removeEventListener(EventListener listener)
575     {
576         if (listener instanceof HttpSessionAttributeListener)
577             _sessionAttributeListeners.remove(listener);
578         if (listener instanceof HttpSessionListener)
579             _sessionListeners.remove(listener);
580         if (listener instanceof HttpSessionIdListener)
581             _sessionIdListeners.remove(listener);
582         removeBean(listener);
583     }
584     
585     /* ------------------------------------------------------------ */
586     /**
587      * Reset statistics values
588      */
589     @ManagedOperation(value="reset statistics", impact="ACTION")
590     public void statsReset()
591     {
592         _sessionsStats.reset(getSessions());
593         _sessionTimeStats.reset();
594     }
595 
596     /* ------------------------------------------------------------ */
597     /**
598      * @param httpOnly
599      *            The httpOnly to set.
600      */
601     public void setHttpOnly(boolean httpOnly)
602     {
603         _httpOnly=httpOnly;
604     }
605 
606     /* ------------------------------------------------------------ */
607     /**
608      * @param metaManager The metaManager used for cross context session management.
609      */
610     @Override
611     public void setSessionIdManager(SessionIdManager metaManager)
612     {
613         updateBean(_sessionIdManager, metaManager);
614         _sessionIdManager=metaManager;
615     }
616 
617     /* ------------------------------------------------------------ */
618     @Override
619     public void setMaxInactiveInterval(int seconds)
620     {
621         _dftMaxIdleSecs=seconds;
622     }
623 
624     /* ------------------------------------------------------------ */
625     public void setRefreshCookieAge(int ageInSeconds)
626     {
627         _refreshCookieAge=ageInSeconds;
628     }
629 
630     /* ------------------------------------------------------------ */
631     public void setSessionCookie(String cookieName)
632     {
633         _sessionCookie=cookieName;
634     }
635 
636     /* ------------------------------------------------------------ */
637     /**
638      * @param sessionHandler
639      *            The sessionHandler to set.
640      */
641     @Override
642     public void setSessionHandler(SessionHandler sessionHandler)
643     {
644         _sessionHandler=sessionHandler;
645     }
646 
647 
648     /* ------------------------------------------------------------ */
649     @Override
650     public void setSessionIdPathParameterName(String param)
651     {
652         _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
653         _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
654     }
655     /* ------------------------------------------------------------ */
656     /**
657      * @param usingCookies
658      *            The usingCookies to set.
659      */
660     public void setUsingCookies(boolean usingCookies)
661     {
662         _usingCookies=usingCookies;
663     }
664 
665 
666     protected abstract void addSession(AbstractSession session);
667 
668     /* ------------------------------------------------------------ */
669     /**
670      * Add the session Registers the session with this manager and registers the
671      * session ID with the sessionIDManager;
672      * @param session the session
673      * @param created true if session was created
674      */
675     protected void addSession(AbstractSession session, boolean created)
676     {
677         synchronized (_sessionIdManager)
678         {
679             _sessionIdManager.addSession(session);
680             addSession(session);
681         }
682 
683         if (created)
684         {
685             _sessionsStats.increment();
686             if (_sessionListeners!=null)
687             {
688                 HttpSessionEvent event=new HttpSessionEvent(session);
689                 for (HttpSessionListener listener : _sessionListeners)
690                     listener.sessionCreated(event);
691             }
692         }
693     }
694 
695     /* ------------------------------------------------------------ */
696     /**
697      * Get a known existing session
698      * @param idInCluster The session ID in the cluster, stripped of any worker name.
699      * @return A Session or null if none exists.
700      */
701     public abstract AbstractSession getSession(String idInCluster);
702 
703     /**
704      * Prepare sessions for session manager shutdown
705      * 
706      * @throws Exception if unable to shutdown sesssions
707      */
708     protected abstract void shutdownSessions() throws Exception;
709 
710 
711     /* ------------------------------------------------------------ */
712     /**
713      * Create a new session instance
714      * @param request the request to build the session from
715      * @return the new session
716      */
717     protected abstract AbstractSession newSession(HttpServletRequest request);
718 
719 
720     /* ------------------------------------------------------------ */
721     /**
722      * @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false.
723      */
724     public boolean isNodeIdInSessionId()
725     {
726         return _nodeIdInSessionId;
727     }
728 
729     /* ------------------------------------------------------------ */
730     /**
731      * @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.
732      */
733     public void setNodeIdInSessionId(boolean nodeIdInSessionId)
734     {
735         _nodeIdInSessionId=nodeIdInSessionId;
736     }
737 
738     /* ------------------------------------------------------------ */
739     /** Remove session from manager
740      * @param session The session to remove
741      * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
742      * {@link SessionIdManager#invalidateAll(String)} should be called.
743      */
744     public void removeSession(HttpSession session, boolean invalidate)
745     {
746         AbstractSession s = ((SessionIf)session).getSession();
747         removeSession(s,invalidate);
748     }
749 
750     /* ------------------------------------------------------------ */
751     /** 
752      * Remove session from manager
753      * @param session The session to remove
754      * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
755      * {@link SessionIdManager#invalidateAll(String)} should be called.
756      * @return if the session was removed 
757      */
758     public boolean removeSession(AbstractSession session, boolean invalidate)
759     {
760         // Remove session from context and global maps
761         boolean removed = removeSession(session.getClusterId());
762 
763         if (removed)
764         {
765             _sessionsStats.decrement();
766             _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0));
767 
768             // Remove session from all context and global id maps
769             _sessionIdManager.removeSession(session);
770             if (invalidate)
771                 _sessionIdManager.invalidateAll(session.getClusterId());
772 
773             if (invalidate && _sessionListeners!=null)
774             {
775                 HttpSessionEvent event=new HttpSessionEvent(session);      
776                 for (int i = _sessionListeners.size()-1; i>=0; i--)
777                 {
778                     _sessionListeners.get(i).sessionDestroyed(event);
779                 }
780             }
781         }
782         
783         return removed;
784     }
785 
786     /* ------------------------------------------------------------ */
787     protected abstract boolean removeSession(String idInCluster);
788 
789     /* ------------------------------------------------------------ */
790     /**
791      * @return maximum amount of time session remained valid
792      */
793     @ManagedAttribute("maximum amount of time sessions have remained active (in s)")
794     public long getSessionTimeMax()
795     {
796         return _sessionTimeStats.getMax();
797     }
798 
799     /* ------------------------------------------------------------ */
800     @Override
801     public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
802     {
803         return __defaultSessionTrackingModes;
804     }
805 
806     /* ------------------------------------------------------------ */
807     @Override
808     public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
809     {
810         return Collections.unmodifiableSet(_sessionTrackingModes);
811     }
812 
813     /* ------------------------------------------------------------ */
814     @Override
815     public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
816     {
817         _sessionTrackingModes=new HashSet<SessionTrackingMode>(sessionTrackingModes);
818         _usingCookies=_sessionTrackingModes.contains(SessionTrackingMode.COOKIE);
819         _usingURLs=_sessionTrackingModes.contains(SessionTrackingMode.URL);
820     }
821 
822     /* ------------------------------------------------------------ */
823     @Override
824     public boolean isUsingURLs()
825     {
826         return _usingURLs;
827     }
828 
829     /* ------------------------------------------------------------ */
830     @Override
831     public SessionCookieConfig getSessionCookieConfig()
832     {
833         return _cookieConfig;
834     }
835 
836     /* ------------------------------------------------------------ */
837     private SessionCookieConfig _cookieConfig =
838         new CookieConfig();
839 
840 
841     /* ------------------------------------------------------------ */
842     /**
843      * @return total amount of time all sessions remained valid
844      */
845     @ManagedAttribute("total time sessions have remained valid")
846     public long getSessionTimeTotal()
847     {
848         return _sessionTimeStats.getTotal();
849     }
850 
851     /* ------------------------------------------------------------ */
852     /**
853      * @return mean amount of time session remained valid
854      */
855     @ManagedAttribute("mean time sessions remain valid (in s)")
856     public double getSessionTimeMean()
857     {
858         return _sessionTimeStats.getMean();
859     }
860 
861     /* ------------------------------------------------------------ */
862     /**
863      * @return standard deviation of amount of time session remained valid
864      */
865     @ManagedAttribute("standard deviation a session remained valid (in s)")
866     public double getSessionTimeStdDev()
867     {
868         return _sessionTimeStats.getStdDev();
869     }
870 
871     /* ------------------------------------------------------------ */
872     /**
873      * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding()
874      */
875     @Override
876     @ManagedAttribute("check remote session id encoding")
877     public boolean isCheckingRemoteSessionIdEncoding()
878     {
879         return _checkingRemoteSessionIdEncoding;
880     }
881 
882     /* ------------------------------------------------------------ */
883     /**
884      * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean)
885      */
886     @Override
887     public void setCheckingRemoteSessionIdEncoding(boolean remote)
888     {
889         _checkingRemoteSessionIdEncoding=remote;
890     }
891     
892     
893     /* ------------------------------------------------------------ */
894     /**
895      * Tell the HttpSessionIdListeners the id changed.
896      * NOTE: this method must be called LAST in subclass overrides, after the session has been updated
897      * with the new id.
898      * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
899      */
900     @Override
901     public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
902     {
903         if (!_sessionIdListeners.isEmpty())
904         {
905             AbstractSession session = getSession(newClusterId);
906             HttpSessionEvent event = new HttpSessionEvent(session);
907             for (HttpSessionIdListener l:_sessionIdListeners)
908             {
909                 l.sessionIdChanged(event, oldClusterId);
910             }
911         }
912 
913     }
914 
915     /**
916      * CookieConfig
917      * 
918      * Implementation of the javax.servlet.SessionCookieConfig.
919      */
920     public final class CookieConfig implements SessionCookieConfig
921     {
922         @Override
923         public String getComment()
924         {
925             return _sessionComment;
926         }
927 
928         @Override
929         public String getDomain()
930         {
931             return _sessionDomain;
932         }
933 
934         @Override
935         public int getMaxAge()
936         {
937             return _maxCookieAge;
938         }
939 
940         @Override
941         public String getName()
942         {
943             return _sessionCookie;
944         }
945 
946         @Override
947         public String getPath()
948         {
949             return _sessionPath;
950         }
951 
952         @Override
953         public boolean isHttpOnly()
954         {
955             return _httpOnly;
956         }
957 
958         @Override
959         public boolean isSecure()
960         {
961             return _secureCookies;
962         }
963 
964         @Override
965         public void setComment(String comment)
966         {  
967             if (_context != null && _context.getContextHandler().isAvailable())
968                 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
969             _sessionComment = comment;
970         }
971 
972         @Override
973         public void setDomain(String domain)
974         {
975             if (_context != null && _context.getContextHandler().isAvailable())
976                 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
977             _sessionDomain=domain;
978         }
979 
980         @Override
981         public void setHttpOnly(boolean httpOnly)
982         {   
983             if (_context != null && _context.getContextHandler().isAvailable())
984                 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
985             _httpOnly=httpOnly;
986         }
987 
988         @Override
989         public void setMaxAge(int maxAge)
990         {               
991             if (_context != null && _context.getContextHandler().isAvailable())
992                 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
993             _maxCookieAge=maxAge;
994         }
995 
996         @Override
997         public void setName(String name)
998         {  
999                 if (_context != null && _context.getContextHandler().isAvailable())
1000                     throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
1001             _sessionCookie=name;
1002         }
1003 
1004         @Override
1005         public void setPath(String path)
1006         {
1007             if (_context != null && _context.getContextHandler().isAvailable())
1008                 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started"); 
1009             _sessionPath=path;
1010         }
1011 
1012         @Override
1013         public void setSecure(boolean secure)
1014         {
1015             if (_context != null && _context.getContextHandler().isAvailable())
1016                 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
1017             _secureCookies=secure;
1018         }
1019     }
1020 
1021     /* ------------------------------------------------------------ */
1022     /* ------------------------------------------------------------ */
1023     /* ------------------------------------------------------------ */
1024     /**
1025      * Interface that any session wrapper should implement so that
1026      * SessionManager may access the Jetty session implementation.
1027      *
1028      */
1029     public interface SessionIf extends HttpSession
1030     {
1031         public AbstractSession getSession();
1032     }
1033 
1034     public void doSessionAttributeListeners(AbstractSession session, String name, Object old, Object value)
1035     {
1036         if (!_sessionAttributeListeners.isEmpty())
1037         {
1038             HttpSessionBindingEvent event=new HttpSessionBindingEvent(session,name,old==null?value:old);
1039 
1040             for (HttpSessionAttributeListener l : _sessionAttributeListeners)
1041             {
1042                 if (old==null)
1043                     l.attributeAdded(event);
1044                 else if (value==null)
1045                     l.attributeRemoved(event);
1046                 else
1047                     l.attributeReplaced(event);
1048             }
1049         }
1050     }
1051 
1052     @Override
1053     @Deprecated
1054     public SessionIdManager getMetaManager()
1055     {
1056         throw new UnsupportedOperationException();
1057     }
1058 }