View Javadoc

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