View Javadoc

1   // ========================================================================
2   // Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.server.session;
15  
16  import static java.lang.Math.round;
17  
18  import java.io.Serializable;
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.Enumeration;
22  import java.util.EventListener;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  
28  import javax.servlet.ServletContext;
29  import javax.servlet.ServletRequest;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpSession;
32  import javax.servlet.http.HttpSessionActivationListener;
33  import javax.servlet.http.HttpSessionAttributeListener;
34  import javax.servlet.http.HttpSessionBindingEvent;
35  import javax.servlet.http.HttpSessionBindingListener;
36  import javax.servlet.http.HttpSessionContext;
37  import javax.servlet.http.HttpSessionEvent;
38  import javax.servlet.http.HttpSessionListener;
39  
40  import org.eclipse.jetty.http.HttpCookie;
41  import org.eclipse.jetty.server.AbstractConnector;
42  import org.eclipse.jetty.server.Request;
43  import org.eclipse.jetty.server.Server;
44  import org.eclipse.jetty.server.SessionIdManager;
45  import org.eclipse.jetty.server.SessionManager;
46  import org.eclipse.jetty.server.handler.ContextHandler;
47  import org.eclipse.jetty.util.LazyList;
48  import org.eclipse.jetty.util.component.AbstractLifeCycle;
49  import org.eclipse.jetty.util.log.Log;
50  import org.eclipse.jetty.util.statistic.CounterStatistic;
51  import org.eclipse.jetty.util.statistic.SampleStatistic;
52  
53  /* ------------------------------------------------------------ */
54  /**
55   * An Abstract implementation of SessionManager. The partial implementation of
56   * SessionManager interface provides the majority of the handling required to
57   * implement a SessionManager. Concrete implementations of SessionManager based
58   * on AbstractSessionManager need only implement the newSession method to return
59   * a specialised version of the Session inner class that provides an attribute
60   * Map.
61   * <p>
62   *
63   * 
64   */
65  public abstract class AbstractSessionManager extends AbstractLifeCycle implements SessionManager
66  {
67      /* ------------------------------------------------------------ */
68      public final static int __distantFuture=60*60*24*7*52*20;
69  
70      private static final HttpSessionContext __nullSessionContext=new NullSessionContext();
71  
72      private boolean _usingCookies=true;
73  
74      /* ------------------------------------------------------------ */
75      // Setting of max inactive interval for new sessions
76      // -1 means no timeout
77      protected int _dftMaxIdleSecs=-1;
78      protected SessionHandler _sessionHandler;
79      protected boolean _httpOnly=false;
80      protected SessionIdManager _sessionIdManager;
81      protected boolean _secureCookies=false;
82      protected Object _sessionAttributeListeners;
83      protected Object _sessionListeners;
84  
85      protected ClassLoader _loader;
86      protected ContextHandler.Context _context;
87      protected String _sessionCookie=__DefaultSessionCookie;
88      protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
89      protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"=";
90      protected String _sessionDomain;
91      protected String _sessionPath;
92      protected int _maxCookieAge=-1;
93      protected int _refreshCookieAge;
94      protected boolean _nodeIdInSessionId;
95      protected boolean _checkingRemoteSessionIdEncoding;
96  
97      protected final CounterStatistic _sessionsStats = new CounterStatistic();
98      protected final SampleStatistic _sessionTimeStats = new SampleStatistic();
99      
100     /* ------------------------------------------------------------ */
101     public AbstractSessionManager()
102     {
103     }
104 
105     /* ------------------------------------------------------------ */
106     public HttpCookie access(HttpSession session,boolean secure)
107     {
108         long now=System.currentTimeMillis();
109 
110         Session s = ((SessionIf)session).getSession();
111         s.access(now);
112 
113         // Do we need to refresh the cookie?
114         if (isUsingCookies() &&
115             (s.isIdChanged() ||
116              (getMaxCookieAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
117             )
118            )
119         {
120             HttpCookie cookie=getSessionCookie(session,_context==null?"/":(_context.getContextPath()),secure);
121             s.cookieSet();
122             s.setIdChanged(false);
123             return cookie;
124         }
125 
126         return null;
127     }
128 
129     /* ------------------------------------------------------------ */
130     public void addEventListener(EventListener listener)
131     {
132         if (listener instanceof HttpSessionAttributeListener)
133             _sessionAttributeListeners=LazyList.add(_sessionAttributeListeners,listener);
134         if (listener instanceof HttpSessionListener)
135             _sessionListeners=LazyList.add(_sessionListeners,listener);
136     }
137 
138     /* ------------------------------------------------------------ */
139     public void clearEventListeners()
140     {
141         _sessionAttributeListeners=null;
142         _sessionListeners=null;
143     }
144 
145     /* ------------------------------------------------------------ */
146     public void complete(HttpSession session)
147     {
148         Session s = ((SessionIf)session).getSession();
149         s.complete();
150     }
151 
152     /* ------------------------------------------------------------ */
153     @Override
154     public void doStart() throws Exception
155     {
156         _context=ContextHandler.getCurrentContext();
157         _loader=Thread.currentThread().getContextClassLoader();
158 
159         if (_sessionIdManager==null)
160         {
161             final Server server=getSessionHandler().getServer();
162             synchronized (server)
163             {
164                 _sessionIdManager=server.getSessionIdManager();
165                 if (_sessionIdManager==null)
166                 {
167                     _sessionIdManager=new HashSessionIdManager();
168                     server.setSessionIdManager(_sessionIdManager);
169                 }
170             }
171         }
172         if (!_sessionIdManager.isStarted())
173             _sessionIdManager.start();
174 
175         // Look for a session cookie name
176         if (_context!=null)
177         {
178             String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty);
179             if (tmp!=null)
180                 _sessionCookie=tmp;
181 
182             tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty);
183             if (tmp!=null)
184                 setSessionIdPathParameterName(tmp);
185 
186             // set up the max session cookie age if it isn't already
187             if (_maxCookieAge==-1)
188             {
189                 tmp=_context.getInitParameter(SessionManager.__MaxAgeProperty);
190                 if (tmp!=null)
191                     _maxCookieAge=Integer.parseInt(tmp.trim());
192             }
193 
194             // set up the session domain if it isn't already
195             if (_sessionDomain==null)
196                 _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty);
197 
198             // set up the sessionPath if it isn't already
199             if (_sessionPath==null)
200                 _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
201             
202             tmp=_context.getInitParameter(SessionManager.__CheckRemoteSessionEncoding);
203             if (tmp!=null)
204                 _checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
205         }
206 
207         super.doStart();
208     }
209 
210     /* ------------------------------------------------------------ */
211     @Override
212     public void doStop() throws Exception
213     {
214         super.doStop();
215 
216         invalidateSessions();
217 
218         _loader=null;
219     }
220 
221     /* ------------------------------------------------------------ */
222     /**
223      * @return Returns the httpOnly.
224      */
225     public boolean getHttpOnly()
226     {
227         return _httpOnly;
228     }
229 
230     /* ------------------------------------------------------------ */
231     public HttpSession getHttpSession(String nodeId)
232     {
233         String cluster_id = getIdManager().getClusterId(nodeId);
234 
235         Session session = getSession(cluster_id);
236         if (session!=null && !session.getNodeId().equals(nodeId))
237             session.setIdChanged(true);
238         return session;
239     }
240 
241     /* ------------------------------------------------------------ */
242     /* ------------------------------------------------------------ */
243     /**
244      * @return Returns the metaManager used for cross context session management
245      */
246     public SessionIdManager getIdManager()
247     {
248         return _sessionIdManager;
249     }
250 
251     /* ------------------------------------------------------------ */
252     public int getMaxCookieAge()
253     {
254         return _maxCookieAge;
255     }
256 
257     /* ------------------------------------------------------------ */
258     /**
259      * @return seconds
260      */
261     public int getMaxInactiveInterval()
262     {
263         return _dftMaxIdleSecs;
264     }
265     
266     /* ------------------------------------------------------------ */
267     /**
268      * @see #getSessionsMax()
269      */
270     @Deprecated
271     public int getMaxSessions()
272     {
273         return getSessionsMax();
274     }
275 
276     /* ------------------------------------------------------------ */
277     /**
278      * @return maximum number of sessions
279      */
280     public int getSessionsMax()
281     {
282         return (int)_sessionsStats.getMax();
283     }
284 
285     /* ------------------------------------------------------------ */
286     /**
287      * @return total number of sessions
288      */
289     public int getSessionsTotal()
290     {
291         return (int)_sessionsStats.getTotal();
292     }
293 
294     /* ------------------------------------------------------------ */
295     /**
296      * @deprecated use {@link #getIdManager()}
297      */
298     @Deprecated
299     public SessionIdManager getMetaManager()
300     {
301         return getIdManager();
302     }
303 
304     /* ------------------------------------------------------------ */
305     /**
306      * @deprecated always returns 0. no replacement available.
307      */
308     @Deprecated
309     public int getMinSessions()
310     {
311         return 0;
312     }
313 
314     /* ------------------------------------------------------------ */
315     public int getRefreshCookieAge()
316     {
317         return _refreshCookieAge;
318     }
319 
320 
321     /* ------------------------------------------------------------ */
322     /**
323      * @return Returns the secureCookies.
324      */
325     public boolean getSecureCookies()
326     {
327         return _secureCookies;
328     }
329 
330     /* ------------------------------------------------------------ */
331     public String getSessionCookie()
332     {
333         return _sessionCookie;
334     }
335 
336     /* ------------------------------------------------------------ */
337     public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
338     {
339         if (isUsingCookies())
340         {
341             String sessionPath = (_sessionPath==null) ? contextPath : _sessionPath;
342             sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
343             String id = getNodeId(session);
344             HttpCookie cookie=new HttpCookie(
345                     _sessionCookie,
346                     id,
347                     _sessionDomain,
348                     sessionPath,
349                     getMaxCookieAge(),
350                     getHttpOnly(),
351                     requestIsSecure&&getSecureCookies());      
352                     
353             return cookie;
354         }
355         return null;
356     }
357 
358     public String getSessionDomain()
359     {
360         return _sessionDomain;
361     }
362 
363     /* ------------------------------------------------------------ */
364     /**
365      * @return Returns the sessionHandler.
366      */
367     public SessionHandler getSessionHandler()
368     {
369         return _sessionHandler;
370     }
371 
372     /* ------------------------------------------------------------ */
373     /**
374      * @deprecated  Need to review if it is needed.
375      */
376     public abstract Map getSessionMap();
377 
378     /* ------------------------------------------------------------ */
379     public String getSessionPath()
380     {
381         return _sessionPath;
382     }
383 
384     /* ------------------------------------------------------------ */
385     public int getSessions()
386     {
387         return (int)_sessionsStats.getCurrent();
388     }
389 
390     /* ------------------------------------------------------------ */
391     public String getSessionIdPathParameterName()
392     {
393         return _sessionIdPathParameterName;
394     }
395 
396     /* ------------------------------------------------------------ */
397     public String getSessionIdPathParameterNamePrefix()
398     {
399         return _sessionIdPathParameterNamePrefix;
400     }
401 
402     /* ------------------------------------------------------------ */
403     /**
404      * @return Returns the usingCookies.
405      */
406     public boolean isUsingCookies()
407     {
408         return _usingCookies;
409     }
410 
411     /* ------------------------------------------------------------ */
412     public boolean isValid(HttpSession session)
413     {
414         Session s = ((SessionIf)session).getSession();
415         return s.isValid();
416     }
417 
418     /* ------------------------------------------------------------ */
419     public String getClusterId(HttpSession session)
420     {
421         Session s = ((SessionIf)session).getSession();
422         return s.getClusterId();
423     }
424 
425     /* ------------------------------------------------------------ */
426     public String getNodeId(HttpSession session)
427     {
428         Session s = ((SessionIf)session).getSession();
429         return s.getNodeId();
430     }
431 
432     /* ------------------------------------------------------------ */
433     /**
434      * Create a new HttpSession for a request
435      */
436     public HttpSession newHttpSession(HttpServletRequest request)
437     {
438         Session session=newSession(request);
439         session.setMaxInactiveInterval(_dftMaxIdleSecs);
440         addSession(session,true);
441         return session;
442     }
443 
444     /* ------------------------------------------------------------ */
445     public void removeEventListener(EventListener listener)
446     {
447         if (listener instanceof HttpSessionAttributeListener)
448             _sessionAttributeListeners=LazyList.remove(_sessionAttributeListeners,listener);
449         if (listener instanceof HttpSessionListener)
450             _sessionListeners=LazyList.remove(_sessionListeners,listener);
451     }
452     
453     /* ------------------------------------------------------------ */
454     /**
455      * @see #statsReset()
456      */
457     @Deprecated
458     public void resetStats()
459     {
460         statsReset();
461     }
462 
463     /* ------------------------------------------------------------ */
464     /**
465      * Reset statistics values
466      */
467     public void statsReset()
468     {
469         _sessionsStats.reset(getSessions());
470         _sessionTimeStats.reset();
471     }
472 
473     /* ------------------------------------------------------------ */
474     /**
475      * @param httpOnly
476      *            The httpOnly to set.
477      */
478     public void setHttpOnly(boolean httpOnly)
479     {
480         _httpOnly=httpOnly;
481     }
482 
483 
484     /* ------------------------------------------------------------ */
485     /**
486      * @param metaManager The metaManager used for cross context session management.
487      */
488     public void setIdManager(SessionIdManager metaManager)
489     {
490         _sessionIdManager=metaManager;
491     }
492 
493     /* ------------------------------------------------------------ */
494     public void setMaxCookieAge(int maxCookieAgeInSeconds)
495     {
496         _maxCookieAge=maxCookieAgeInSeconds;
497 
498         if (_maxCookieAge>0 && _refreshCookieAge==0)
499             _refreshCookieAge=_maxCookieAge/3;
500 
501     }
502 
503     /* ------------------------------------------------------------ */
504     /**
505      * @param seconds
506      */
507     public void setMaxInactiveInterval(int seconds)
508     {
509         _dftMaxIdleSecs=seconds;
510     }
511 
512     /* ------------------------------------------------------------ */
513     /**
514      * @deprecated use {@link #setIdManager(SessionIdManager)}
515      */
516     @Deprecated
517     public void setMetaManager(SessionIdManager metaManager)
518     {
519         setIdManager(metaManager);
520     }
521 
522     /* ------------------------------------------------------------ */
523     public void setRefreshCookieAge(int ageInSeconds)
524     {
525         _refreshCookieAge=ageInSeconds;
526     }
527 
528 
529     /* ------------------------------------------------------------ */
530     /**
531      * Set if the session manager should use SecureCookies.
532      * A secure cookie will only be sent by a browser on a secure (https) connection to 
533      * avoid the concern of cookies being intercepted on non secure channels.
534      * For the cookie to be issued as secure, the {@link ServletRequest#isSecure()} method must return true.
535      * If SSL offload is used, then the {@link AbstractConnector#customize(org.eclipse.jetty.io.EndPoint, Request)}
536      * method can be used to force the request to be https, or the {@link AbstractConnector#setForwarded(boolean)}
537      * can be set to true, so that the X-Forwarded-Proto header is respected.
538      * <p>
539      * If secure session cookies are used, then a session may not be shared between http and https requests.
540      * 
541      * @param secureCookies If true, use secure cookies.
542      */
543     public void setSecureCookies(boolean secureCookies)
544     {
545         _secureCookies=secureCookies;
546     }
547 
548     public void setSessionCookie(String cookieName)
549     {
550         _sessionCookie=cookieName;
551     }
552 
553     public void setSessionDomain(String domain)
554     {
555         _sessionDomain=domain;
556     }
557 
558     /* ------------------------------------------------------------ */
559     /**
560      * @param sessionHandler
561      *            The sessionHandler to set.
562      */
563     public void setSessionHandler(SessionHandler sessionHandler)
564     {
565         _sessionHandler=sessionHandler;
566     }
567 
568     /* ------------------------------------------------------------ */
569     public void setSessionPath(String path)
570     {
571         _sessionPath=path;
572     }
573 
574     /* ------------------------------------------------------------ */
575     public void setSessionIdPathParameterName(String param)
576     {
577         _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
578         _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
579     }
580     /* ------------------------------------------------------------ */
581     /**
582      * @param usingCookies
583      *            The usingCookies to set.
584      */
585     public void setUsingCookies(boolean usingCookies)
586     {
587         _usingCookies=usingCookies;
588     }
589 
590 
591     protected abstract void addSession(Session session);
592 
593     /* ------------------------------------------------------------ */
594     /**
595      * Add the session Registers the session with this manager and registers the
596      * session ID with the sessionIDManager;
597      */
598     protected void addSession(Session session, boolean created)
599     {
600         synchronized (_sessionIdManager)
601         {
602             _sessionIdManager.addSession(session);
603             addSession(session);
604         }
605 
606         if (created)
607         {
608             _sessionsStats.increment();
609             if (_sessionListeners!=null)
610             {
611                 HttpSessionEvent event=new HttpSessionEvent(session);
612                 for (int i=0; i<LazyList.size(_sessionListeners); i++)
613                     ((HttpSessionListener)LazyList.get(_sessionListeners,i)).sessionCreated(event);
614             }
615         }
616     }
617 
618     /* ------------------------------------------------------------ */
619     /**
620      * Get a known existing session
621      * @param idInCluster The session ID in the cluster, stripped of any worker name.
622      * @return A Session or null if none exists.
623      */
624     public abstract Session getSession(String idInCluster);
625 
626     protected abstract void invalidateSessions();
627 
628 
629     /* ------------------------------------------------------------ */
630     /**
631      * Create a new session instance
632      * @param request
633      * @return the new session
634      */
635     protected abstract Session newSession(HttpServletRequest request);
636 
637 
638     /* ------------------------------------------------------------ */
639     /**
640      * @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false.
641      */
642     public boolean isNodeIdInSessionId()
643     {
644         return _nodeIdInSessionId;
645     }
646 
647     /* ------------------------------------------------------------ */
648     /**
649      * @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.
650      */
651     public void setNodeIdInSessionId(boolean nodeIdInSessionId)
652     {
653         _nodeIdInSessionId=nodeIdInSessionId;
654     }
655 
656     /* ------------------------------------------------------------ */
657     /** Remove session from manager
658      * @param session The session to remove
659      * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
660      * {@link SessionIdManager#invalidateAll(String)} should be called.
661      */
662     public void removeSession(HttpSession session, boolean invalidate)
663     {
664         Session s = ((SessionIf)session).getSession();
665         removeSession(s,invalidate);
666     }
667 
668     /* ------------------------------------------------------------ */
669     /** Remove session from manager
670      * @param session The session to remove
671      * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
672      * {@link SessionIdManager#invalidateAll(String)} should be called.
673      */
674     public void removeSession(Session session, boolean invalidate)
675     {
676         // Remove session from context and global maps
677         boolean removed = removeSession(session.getClusterId());
678         
679         if (removed)
680         {
681             _sessionsStats.decrement();
682             _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0));
683             
684             // Remove session from all context and global id maps
685             _sessionIdManager.removeSession(session);
686             if (invalidate)
687                 _sessionIdManager.invalidateAll(session.getClusterId());
688             
689             if (invalidate && _sessionListeners!=null)
690             {
691                 HttpSessionEvent event=new HttpSessionEvent(session);
692                 for (int i=LazyList.size(_sessionListeners); i-->0;)
693                     ((HttpSessionListener)LazyList.get(_sessionListeners,i)).sessionDestroyed(event);
694             }
695         }
696     }
697 
698     /* ------------------------------------------------------------ */
699     protected abstract boolean removeSession(String idInCluster);
700     
701     /* ------------------------------------------------------------ */
702     /**
703      * @return maximum amount of time session remained valid
704      */
705     public long getSessionTimeMax()
706     {
707         return _sessionTimeStats.getMax();
708     }
709 
710     /* ------------------------------------------------------------ */
711     /**
712      * @return total amount of time all sessions remained valid
713      */
714     public long getSessionTimeTotal()
715     {
716         return _sessionTimeStats.getTotal();
717     }
718     
719     /* ------------------------------------------------------------ */
720     /**
721      * @return mean amount of time session remained valid
722      */
723     public double getSessionTimeMean()
724     {
725         return _sessionTimeStats.getMean();
726     }
727     
728     /* ------------------------------------------------------------ */
729     /**
730      * @return standard deviation of amount of time session remained valid
731      */
732     public double getSessionTimeStdDev()
733     {
734         return _sessionTimeStats.getStdDev();
735     }
736 
737     /* ------------------------------------------------------------ */
738     /**
739      * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding()
740      */
741     public boolean isCheckingRemoteSessionIdEncoding()
742     {
743         return _checkingRemoteSessionIdEncoding;
744     }
745 
746     /* ------------------------------------------------------------ */
747     /**
748      * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean)
749      */
750     public void setCheckingRemoteSessionIdEncoding(boolean remote)
751     {
752         _checkingRemoteSessionIdEncoding=remote;
753     }
754 
755     /* ------------------------------------------------------------ */
756     /**
757      * Null returning implementation of HttpSessionContext
758      *
759      * 
760      */
761     public static class NullSessionContext implements HttpSessionContext
762     {
763         /* ------------------------------------------------------------ */
764         private NullSessionContext()
765         {
766         }
767 
768         /* ------------------------------------------------------------ */
769         /**
770          * @deprecated From HttpSessionContext
771          */
772         @Deprecated
773         public Enumeration getIds()
774         {
775             return Collections.enumeration(Collections.EMPTY_LIST);
776         }
777 
778         /* ------------------------------------------------------------ */
779         /**
780          * @deprecated From HttpSessionContext
781          */
782         @Deprecated
783         public HttpSession getSession(String id)
784         {
785             return null;
786         }
787     }
788 
789     /* ------------------------------------------------------------ */
790     /* ------------------------------------------------------------ */
791     /* ------------------------------------------------------------ */
792     /**
793      * Interface that any session wrapper should implement so that
794      * SessionManager may access the Jetty session implementation.
795      *
796      */
797     public interface SessionIf extends HttpSession
798     {
799         public Session getSession();
800     }
801 
802     /* ------------------------------------------------------------ */
803     /**
804      *
805      * <p>
806      * Implements {@link javax.servlet.http.HttpSession} from the <code>javax.servlet</code> package.
807      * </p>
808      * 
809      *
810      */
811     public abstract class Session implements SessionIf, Serializable
812     {
813         protected final String _clusterId; // ID unique within cluster
814         protected final String _nodeId;    // ID unique within node
815         protected final Map<String,Object> _attributes=new HashMap<String, Object>();
816         protected boolean _idChanged;
817         protected final long _created;
818         protected long _cookieSet;
819         protected long _accessed;
820         protected long _lastAccessed;
821         protected boolean _invalid;
822         protected boolean _doInvalidate;
823         protected long _maxIdleMs=_dftMaxIdleSecs>0?_dftMaxIdleSecs*1000:-1;
824         protected boolean _newSession;
825         protected int _requests;
826 
827         /* ------------------------------------------------------------- */
828         protected Session(HttpServletRequest request)
829         {
830             _newSession=true;
831             _created=System.currentTimeMillis();
832             _clusterId=_sessionIdManager.newSessionId(request,_created);
833             _nodeId=_sessionIdManager.getNodeId(_clusterId,request);
834             _accessed=_created;
835             _lastAccessed=_created;
836             _requests=1;
837             Log.debug("new session & id "+_nodeId+" "+_clusterId);
838         }
839 
840         /* ------------------------------------------------------------- */
841         protected Session(long created, long accessed, String clusterId)
842         {
843             _created=created;
844             _clusterId=clusterId;
845             _nodeId=_sessionIdManager.getNodeId(_clusterId,null);
846             _accessed=accessed;
847             _lastAccessed=accessed;
848             _requests=1;
849             Log.debug("new session "+_nodeId+" "+_clusterId);
850         }
851         
852         /* ------------------------------------------------------------- */
853         /**
854          * @return True is the session is invalid or passivated.
855          */
856         protected boolean isNotAvailable()
857         {
858             return _invalid;
859         }
860         
861         /* ------------------------------------------------------------- */
862         public Session getSession()
863         {
864             return this;
865         }
866 
867         /* ------------------------------------------------------------ */
868         public Object getAttribute(String name)
869         {
870             synchronized (Session.this)
871             {
872                 if (isNotAvailable())
873                     throw new IllegalStateException();
874 
875                 return _attributes.get(name);
876             }
877         }
878 
879         /* ------------------------------------------------------------ */
880         public Enumeration getAttributeNames()
881         {
882             synchronized (Session.this)
883             {
884                 if (isNotAvailable())
885                     throw new IllegalStateException();
886                 List names=_attributes==null?Collections.EMPTY_LIST:new ArrayList(_attributes.keySet());
887                 return Collections.enumeration(names);
888             }
889         }
890 
891         /* ------------------------------------------------------------- */
892         public long getCookieSetTime()
893         {
894             return _cookieSet;
895         }
896 
897         /* ------------------------------------------------------------- */
898         public long getCreationTime() throws IllegalStateException
899         {
900             if (isNotAvailable())
901                 throw new IllegalStateException();
902             return _created;
903         }
904 
905         /* ------------------------------------------------------------ */
906         public String getId() throws IllegalStateException
907         {
908             return _nodeIdInSessionId?_nodeId:_clusterId;
909         }
910 
911         /* ------------------------------------------------------------- */
912         protected String getNodeId()
913         {
914             return _nodeId;
915         }
916 
917         /* ------------------------------------------------------------- */
918         protected String getClusterId()
919         {
920             return _clusterId;
921         }
922 
923         /* ------------------------------------------------------------- */
924         public long getLastAccessedTime() throws IllegalStateException
925         {
926             if (isNotAvailable())
927                 throw new IllegalStateException();
928             return _lastAccessed;
929         }
930 
931         /* ------------------------------------------------------------- */
932         public int getMaxInactiveInterval()
933         {
934             if (isNotAvailable())
935                 throw new IllegalStateException();
936             return (int)(_maxIdleMs/1000);
937         }
938 
939         /* ------------------------------------------------------------ */
940         /*
941          * @see javax.servlet.http.HttpSession#getServletContext()
942          */
943         public ServletContext getServletContext()
944         {
945             return _context;
946         }
947 
948         /* ------------------------------------------------------------- */
949         /**
950          * @deprecated
951          */
952         @Deprecated
953         public HttpSessionContext getSessionContext() throws IllegalStateException
954         {
955             if (isNotAvailable())
956                 throw new IllegalStateException();
957             return __nullSessionContext;
958         }
959 
960         /* ------------------------------------------------------------- */
961         /**
962          * @deprecated As of Version 2.2, this method is replaced by
963          *             {@link #getAttribute}
964          */
965         @Deprecated
966         public Object getValue(String name) throws IllegalStateException
967         {
968             return getAttribute(name);
969         }
970 
971         /* ------------------------------------------------------------- */
972         /**
973          * @deprecated As of Version 2.2, this method is replaced by
974          *             {@link #getAttributeNames}
975          */
976         @Deprecated
977         public String[] getValueNames() throws IllegalStateException
978         {
979             synchronized(Session.this)
980             {
981                 if (isNotAvailable())
982                     throw new IllegalStateException();
983                 if (_attributes==null)
984                     return new String[0];
985                 String[] a=new String[_attributes.size()];
986                 return (String[])_attributes.keySet().toArray(a);
987             }
988         }
989 
990         /* ------------------------------------------------------------ */
991         protected void access(long time)
992         {
993             synchronized(Session.this)
994             {
995                 if (!_invalid) 
996                 {
997                     _newSession=false;
998                     _lastAccessed=_accessed;
999                     _accessed=time;
1000                 
1001                     if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time) 
1002                     {
1003                         invalidate();
1004                     }
1005                     else
1006                     {
1007                         _requests++;
1008                     }
1009                 }
1010             }
1011         }
1012 
1013         /* ------------------------------------------------------------ */
1014         protected void complete()
1015         {
1016             synchronized(Session.this)
1017             {
1018                 _requests--;
1019                 if (_doInvalidate && _requests<=0  )
1020                     doInvalidate();
1021             }
1022         }
1023 
1024 
1025         /* ------------------------------------------------------------- */
1026         protected void timeout() throws IllegalStateException
1027         {
1028             // remove session from context and invalidate other sessions with same ID.
1029             removeSession(this,true);
1030 
1031             // Notify listeners and unbind values
1032             synchronized (Session.this)
1033             {
1034                 if (!_invalid)
1035                 {
1036                     if (_requests<=0)
1037                         doInvalidate();
1038                     else
1039                         _doInvalidate=true;
1040                 }
1041             }
1042         }
1043 
1044         /* ------------------------------------------------------------- */
1045         public void invalidate() throws IllegalStateException
1046         {
1047             // remove session from context and invalidate other sessions with same ID.
1048             removeSession(this,true);
1049             doInvalidate();
1050         }
1051 
1052         /* ------------------------------------------------------------- */
1053         protected void doInvalidate() throws IllegalStateException
1054         {
1055             try
1056             {
1057                 Log.debug("invalidate ",_clusterId);
1058                 // Notify listeners and unbind values
1059                 if (isNotAvailable())
1060                     throw new IllegalStateException();
1061 
1062                 while (_attributes!=null && _attributes.size()>0)
1063                 {
1064                     ArrayList keys;
1065                     synchronized (Session.this)
1066                     {
1067                         keys=new ArrayList(_attributes.keySet());
1068                     }
1069 
1070                     Iterator iter=keys.iterator();
1071                     while (iter.hasNext())
1072                     {
1073                         String key=(String)iter.next();
1074 
1075                         Object value;
1076                         synchronized (Session.this)
1077                         {
1078                             value=_attributes.remove(key);
1079                         }
1080                         unbindValue(key,value);
1081 
1082                         if (_sessionAttributeListeners!=null)
1083                         {
1084                             HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,key,value);
1085 
1086                             for (int i=0; i<LazyList.size(_sessionAttributeListeners); i++)
1087                                 ((HttpSessionAttributeListener)LazyList.get(_sessionAttributeListeners,i)).attributeRemoved(event);
1088                         }
1089                     }
1090                 }
1091             }
1092             finally
1093             {
1094                 // mark as invalid
1095                 _invalid=true;
1096             }
1097         }
1098 
1099         /* ------------------------------------------------------------- */
1100         public boolean isIdChanged()
1101         {
1102             return _idChanged;
1103         }
1104 
1105         /* ------------------------------------------------------------- */
1106         public boolean isNew() throws IllegalStateException
1107         {
1108             if (isNotAvailable())
1109                 throw new IllegalStateException();
1110             return _newSession;
1111         }
1112 
1113         /* ------------------------------------------------------------- */
1114         /**
1115          * @deprecated As of Version 2.2, this method is replaced by
1116          *             {@link #setAttribute}
1117          */
1118         @Deprecated
1119         public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException
1120         {
1121             setAttribute(name,value);
1122         }
1123 
1124         /* ------------------------------------------------------------ */
1125         public void removeAttribute(String name)
1126         {
1127             Object old;
1128             synchronized(Session.this)
1129             {
1130                 if (isNotAvailable())
1131                     throw new IllegalStateException();
1132                 if (_attributes==null)
1133                     return;
1134 
1135                 old=_attributes.remove(name);
1136             }
1137 
1138             if (old!=null)
1139             {
1140                 unbindValue(name,old);
1141                 if (_sessionAttributeListeners!=null)
1142                 {
1143                     HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,name,old);
1144 
1145                     for (int i=0; i<LazyList.size(_sessionAttributeListeners); i++)
1146                         ((HttpSessionAttributeListener)LazyList.get(_sessionAttributeListeners,i)).attributeRemoved(event);
1147                 }
1148             }
1149 
1150         }
1151 
1152         /* ------------------------------------------------------------- */
1153         /**
1154          * @deprecated As of Version 2.2, this method is replaced by
1155          *             {@link #removeAttribute}
1156          */
1157         @Deprecated
1158         public void removeValue(java.lang.String name) throws IllegalStateException
1159         {
1160             removeAttribute(name);
1161         }
1162 
1163         /* ------------------------------------------------------------ */
1164         public void setAttribute(String name, Object value)
1165         {
1166             Object old_value=null;
1167             synchronized (Session.this)
1168             {
1169                 if (value==null)
1170                 {
1171                     removeAttribute(name);
1172                     return;
1173                 }
1174 
1175                 if (isNotAvailable())
1176                     throw new IllegalStateException();
1177                 old_value=_attributes.put(name,value);
1178             }
1179             
1180             if (old_value==null || !value.equals(old_value))
1181             {
1182                 unbindValue(name,old_value);
1183                 bindValue(name,value);
1184 
1185                 if (_sessionAttributeListeners!=null)
1186                 {
1187                     HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,name,old_value==null?value:old_value);
1188 
1189                     for (int i=0; i<LazyList.size(_sessionAttributeListeners); i++)
1190                     {
1191                         HttpSessionAttributeListener l=(HttpSessionAttributeListener)LazyList.get(_sessionAttributeListeners,i);
1192 
1193                         if (old_value==null)
1194                             l.attributeAdded(event);
1195                         else
1196                             l.attributeReplaced(event);
1197                     }
1198                 }
1199             }
1200         }
1201 
1202         /* ------------------------------------------------------------- */
1203         public void setIdChanged(boolean changed)
1204         {
1205             _idChanged=changed;
1206         }
1207 
1208         /* ------------------------------------------------------------- */
1209         public void setMaxInactiveInterval(int secs)
1210         {
1211             _maxIdleMs=(long)secs*1000;
1212         }
1213 
1214         /* ------------------------------------------------------------- */
1215         @Override
1216         public String toString()
1217         {
1218             return this.getClass().getName()+":"+getId()+"@"+hashCode();
1219         }
1220 
1221         /* ------------------------------------------------------------- */
1222         /** If value implements HttpSessionBindingListener, call valueBound() */
1223         protected void bindValue(java.lang.String name, Object value)
1224         {
1225             if (value!=null&&value instanceof HttpSessionBindingListener)
1226                 ((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this,name));
1227         }
1228 
1229         /* ------------------------------------------------------------ */
1230         protected boolean isValid()
1231         {
1232             return !_invalid;
1233         }
1234 
1235         /* ------------------------------------------------------------- */
1236         protected void cookieSet()
1237         {
1238             _cookieSet=_accessed;
1239         }
1240 
1241         /* ------------------------------------------------------------- */
1242         /** If value implements HttpSessionBindingListener, call valueUnbound() */
1243         protected void unbindValue(java.lang.String name, Object value)
1244         {
1245             if (value!=null&&value instanceof HttpSessionBindingListener)
1246                 ((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this,name));
1247         }
1248 
1249         /* ------------------------------------------------------------- */
1250         protected void willPassivate()
1251         {
1252             synchronized(Session.this)
1253             {
1254                 HttpSessionEvent event = new HttpSessionEvent(this);
1255                 for (Iterator iter = _attributes.values().iterator(); iter.hasNext();)
1256                 {
1257                     Object value = iter.next();
1258                     if (value instanceof HttpSessionActivationListener)
1259                     {
1260                         HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
1261                         listener.sessionWillPassivate(event);
1262                     }
1263                 }
1264             }
1265         }
1266 
1267         /* ------------------------------------------------------------- */
1268         protected void didActivate()
1269         {
1270             synchronized(Session.this)
1271             {
1272                 HttpSessionEvent event = new HttpSessionEvent(this);
1273                 for (Iterator iter = _attributes.values().iterator(); iter.hasNext();)
1274                 {
1275                     Object value = iter.next();
1276                     if (value instanceof HttpSessionActivationListener)
1277                     {
1278                         HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
1279                         listener.sessionDidActivate(event);
1280                     }
1281                 }
1282             }
1283         }
1284     }
1285 }