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