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     /**
570      * @see org.eclipse.jetty.server.SessionManager#setSessionPath(java.lang.String)
571      */
572     public void setSessionPath(String path)
573     {
574         _sessionPath=path;
575     }
576 
577     /* ------------------------------------------------------------ */
578     public void setSessionIdPathParameterName(String param)
579     {
580         _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
581         _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
582     }
583     /* ------------------------------------------------------------ */
584     /**
585      * @param usingCookies
586      *            The usingCookies to set.
587      */
588     public void setUsingCookies(boolean usingCookies)
589     {
590         _usingCookies=usingCookies;
591     }
592 
593 
594     protected abstract void addSession(Session session);
595 
596     /* ------------------------------------------------------------ */
597     /**
598      * Add the session Registers the session with this manager and registers the
599      * session ID with the sessionIDManager;
600      */
601     protected void addSession(Session session, boolean created)
602     {
603         synchronized (_sessionIdManager)
604         {
605             _sessionIdManager.addSession(session);
606             addSession(session);
607         }
608 
609         if (created)
610         {
611             _sessionsStats.increment();
612             if (_sessionListeners!=null)
613             {
614                 HttpSessionEvent event=new HttpSessionEvent(session);
615                 for (int i=0; i<LazyList.size(_sessionListeners); i++)
616                     ((HttpSessionListener)LazyList.get(_sessionListeners,i)).sessionCreated(event);
617             }
618         }
619     }
620 
621     /* ------------------------------------------------------------ */
622     /**
623      * Get a known existing session
624      * @param idInCluster The session ID in the cluster, stripped of any worker name.
625      * @return A Session or null if none exists.
626      */
627     public abstract Session getSession(String idInCluster);
628 
629     protected abstract void invalidateSessions();
630 
631 
632     /* ------------------------------------------------------------ */
633     /**
634      * Create a new session instance
635      * @param request
636      * @return the new session
637      */
638     protected abstract Session newSession(HttpServletRequest request);
639 
640 
641     /* ------------------------------------------------------------ */
642     /**
643      * @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false.
644      */
645     public boolean isNodeIdInSessionId()
646     {
647         return _nodeIdInSessionId;
648     }
649 
650     /* ------------------------------------------------------------ */
651     /**
652      * @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.
653      */
654     public void setNodeIdInSessionId(boolean nodeIdInSessionId)
655     {
656         _nodeIdInSessionId=nodeIdInSessionId;
657     }
658 
659     /* ------------------------------------------------------------ */
660     /** Remove session from manager
661      * @param session The session to remove
662      * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
663      * {@link SessionIdManager#invalidateAll(String)} should be called.
664      */
665     public void removeSession(HttpSession session, boolean invalidate)
666     {
667         Session s = ((SessionIf)session).getSession();
668         removeSession(s,invalidate);
669     }
670 
671     /* ------------------------------------------------------------ */
672     /** Remove session from manager
673      * @param session The session to remove
674      * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
675      * {@link SessionIdManager#invalidateAll(String)} should be called.
676      */
677     public void removeSession(Session session, boolean invalidate)
678     {
679         // Remove session from context and global maps
680         boolean removed = removeSession(session.getClusterId());
681         
682         if (removed)
683         {
684             _sessionsStats.decrement();
685             _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0));
686             
687             // Remove session from all context and global id maps
688             _sessionIdManager.removeSession(session);
689             if (invalidate)
690                 _sessionIdManager.invalidateAll(session.getClusterId());
691             
692             if (invalidate && _sessionListeners!=null)
693             {
694                 HttpSessionEvent event=new HttpSessionEvent(session);
695                 for (int i=LazyList.size(_sessionListeners); i-->0;)
696                     ((HttpSessionListener)LazyList.get(_sessionListeners,i)).sessionDestroyed(event);
697             }
698         }
699     }
700 
701     /* ------------------------------------------------------------ */
702     protected abstract boolean removeSession(String idInCluster);
703     
704     /* ------------------------------------------------------------ */
705     /**
706      * @return maximum amount of time session remained valid
707      */
708     public long getSessionTimeMax()
709     {
710         return _sessionTimeStats.getMax();
711     }
712 
713     /* ------------------------------------------------------------ */
714     /**
715      * @return total amount of time all sessions remained valid
716      */
717     public long getSessionTimeTotal()
718     {
719         return _sessionTimeStats.getTotal();
720     }
721     
722     /* ------------------------------------------------------------ */
723     /**
724      * @return mean amount of time session remained valid
725      */
726     public double getSessionTimeMean()
727     {
728         return _sessionTimeStats.getMean();
729     }
730     
731     /* ------------------------------------------------------------ */
732     /**
733      * @return standard deviation of amount of time session remained valid
734      */
735     public double getSessionTimeStdDev()
736     {
737         return _sessionTimeStats.getStdDev();
738     }
739 
740     /* ------------------------------------------------------------ */
741     /**
742      * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding()
743      */
744     public boolean isCheckingRemoteSessionIdEncoding()
745     {
746         return _checkingRemoteSessionIdEncoding;
747     }
748 
749     /* ------------------------------------------------------------ */
750     /**
751      * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean)
752      */
753     public void setCheckingRemoteSessionIdEncoding(boolean remote)
754     {
755         _checkingRemoteSessionIdEncoding=remote;
756     }
757 
758     /* ------------------------------------------------------------ */
759     /**
760      * Null returning implementation of HttpSessionContext
761      *
762      * 
763      */
764     public static class NullSessionContext implements HttpSessionContext
765     {
766         /* ------------------------------------------------------------ */
767         private NullSessionContext()
768         {
769         }
770 
771         /* ------------------------------------------------------------ */
772         /**
773          * @deprecated From HttpSessionContext
774          */
775         @Deprecated
776         public Enumeration getIds()
777         {
778             return Collections.enumeration(Collections.EMPTY_LIST);
779         }
780 
781         /* ------------------------------------------------------------ */
782         /**
783          * @deprecated From HttpSessionContext
784          */
785         @Deprecated
786         public HttpSession getSession(String id)
787         {
788             return null;
789         }
790     }
791 
792     /* ------------------------------------------------------------ */
793     /* ------------------------------------------------------------ */
794     /* ------------------------------------------------------------ */
795     /**
796      * Interface that any session wrapper should implement so that
797      * SessionManager may access the Jetty session implementation.
798      *
799      */
800     public interface SessionIf extends HttpSession
801     {
802         public Session getSession();
803     }
804 
805     /* ------------------------------------------------------------ */
806     /**
807      *
808      * <p>
809      * Implements {@link javax.servlet.http.HttpSession} from the <code>javax.servlet</code> package.
810      * </p>
811      * 
812      *
813      */
814     public abstract class Session implements SessionIf, Serializable
815     {
816         protected final String _clusterId; // ID unique within cluster
817         protected final String _nodeId;    // ID unique within node
818         protected final Map<String,Object> _attributes=new HashMap<String, Object>();
819         protected boolean _idChanged;
820         protected final long _created;
821         protected long _cookieSet;
822         protected long _accessed;
823         protected long _lastAccessed;
824         protected boolean _invalid;
825         protected boolean _doInvalidate;
826         protected long _maxIdleMs=_dftMaxIdleSecs>0?_dftMaxIdleSecs*1000:-1;
827         protected boolean _newSession;
828         protected int _requests;
829 
830         /* ------------------------------------------------------------- */
831         protected Session(HttpServletRequest request)
832         {
833             _newSession=true;
834             _created=System.currentTimeMillis();
835             _clusterId=_sessionIdManager.newSessionId(request,_created);
836             _nodeId=_sessionIdManager.getNodeId(_clusterId,request);
837             _accessed=_created;
838             _lastAccessed=_created;
839             _requests=1;
840             Log.debug("new session & id "+_nodeId+" "+_clusterId);
841         }
842 
843         /* ------------------------------------------------------------- */
844         protected Session(long created, long accessed, String clusterId)
845         {
846             _created=created;
847             _clusterId=clusterId;
848             _nodeId=_sessionIdManager.getNodeId(_clusterId,null);
849             _accessed=accessed;
850             _lastAccessed=accessed;
851             _requests=1;
852             Log.debug("new session "+_nodeId+" "+_clusterId);
853         }
854         
855         /* ------------------------------------------------------------- */
856         /**
857          * @return True is the session is invalid or passivated.
858          */
859         protected boolean isNotAvailable()
860         {
861             return _invalid;
862         }
863         
864         /* ------------------------------------------------------------- */
865         public Session getSession()
866         {
867             return this;
868         }
869 
870         /* ------------------------------------------------------------ */
871         public Object getAttribute(String name)
872         {
873             synchronized (Session.this)
874             {
875                 if (isNotAvailable())
876                     throw new IllegalStateException();
877 
878                 return _attributes.get(name);
879             }
880         }
881 
882         /* ------------------------------------------------------------ */
883         public Enumeration getAttributeNames()
884         {
885             synchronized (Session.this)
886             {
887                 if (isNotAvailable())
888                     throw new IllegalStateException();
889                 List names=_attributes==null?Collections.EMPTY_LIST:new ArrayList(_attributes.keySet());
890                 return Collections.enumeration(names);
891             }
892         }
893 
894         /* ------------------------------------------------------------- */
895         public long getCookieSetTime()
896         {
897             return _cookieSet;
898         }
899 
900         /* ------------------------------------------------------------- */
901         public long getCreationTime() throws IllegalStateException
902         {
903             if (isNotAvailable())
904                 throw new IllegalStateException();
905             return _created;
906         }
907 
908         /* ------------------------------------------------------------ */
909         public String getId() throws IllegalStateException
910         {
911             return _nodeIdInSessionId?_nodeId:_clusterId;
912         }
913 
914         /* ------------------------------------------------------------- */
915         protected String getNodeId()
916         {
917             return _nodeId;
918         }
919 
920         /* ------------------------------------------------------------- */
921         protected String getClusterId()
922         {
923             return _clusterId;
924         }
925 
926         /* ------------------------------------------------------------- */
927         public long getLastAccessedTime() throws IllegalStateException
928         {
929             if (isNotAvailable())
930                 throw new IllegalStateException();
931             return _lastAccessed;
932         }
933 
934         /* ------------------------------------------------------------- */
935         public int getMaxInactiveInterval()
936         {
937             if (isNotAvailable())
938                 throw new IllegalStateException();
939             return (int)(_maxIdleMs/1000);
940         }
941 
942         /* ------------------------------------------------------------ */
943         /*
944          * @see javax.servlet.http.HttpSession#getServletContext()
945          */
946         public ServletContext getServletContext()
947         {
948             return _context;
949         }
950 
951         /* ------------------------------------------------------------- */
952         /**
953          * @deprecated
954          */
955         @Deprecated
956         public HttpSessionContext getSessionContext() throws IllegalStateException
957         {
958             if (isNotAvailable())
959                 throw new IllegalStateException();
960             return __nullSessionContext;
961         }
962 
963         /* ------------------------------------------------------------- */
964         /**
965          * @deprecated As of Version 2.2, this method is replaced by
966          *             {@link #getAttribute}
967          */
968         @Deprecated
969         public Object getValue(String name) throws IllegalStateException
970         {
971             return getAttribute(name);
972         }
973 
974         /* ------------------------------------------------------------- */
975         /**
976          * @deprecated As of Version 2.2, this method is replaced by
977          *             {@link #getAttributeNames}
978          */
979         @Deprecated
980         public String[] getValueNames() throws IllegalStateException
981         {
982             synchronized(Session.this)
983             {
984                 if (isNotAvailable())
985                     throw new IllegalStateException();
986                 if (_attributes==null)
987                     return new String[0];
988                 String[] a=new String[_attributes.size()];
989                 return (String[])_attributes.keySet().toArray(a);
990             }
991         }
992 
993         /* ------------------------------------------------------------ */
994         protected void access(long time)
995         {
996             synchronized(Session.this)
997             {
998                 if (!_invalid) 
999                 {
1000                     _newSession=false;
1001                     _lastAccessed=_accessed;
1002                     _accessed=time;
1003                 
1004                     if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time) 
1005                     {
1006                         invalidate();
1007                     }
1008                     else
1009                     {
1010                         _requests++;
1011                     }
1012                 }
1013             }
1014         }
1015 
1016         /* ------------------------------------------------------------ */
1017         protected void complete()
1018         {
1019             synchronized(Session.this)
1020             {
1021                 _requests--;
1022                 if (_doInvalidate && _requests<=0  )
1023                     doInvalidate();
1024             }
1025         }
1026 
1027 
1028         /* ------------------------------------------------------------- */
1029         protected void timeout() throws IllegalStateException
1030         {
1031             // remove session from context and invalidate other sessions with same ID.
1032             removeSession(this,true);
1033 
1034             // Notify listeners and unbind values
1035             synchronized (Session.this)
1036             {
1037                 if (!_invalid)
1038                 {
1039                     if (_requests<=0)
1040                         doInvalidate();
1041                     else
1042                         _doInvalidate=true;
1043                 }
1044             }
1045         }
1046 
1047         /* ------------------------------------------------------------- */
1048         public void invalidate() throws IllegalStateException
1049         {
1050             // remove session from context and invalidate other sessions with same ID.
1051             removeSession(this,true);
1052             doInvalidate();
1053         }
1054 
1055         /* ------------------------------------------------------------- */
1056         protected void doInvalidate() throws IllegalStateException
1057         {
1058             try
1059             {
1060                 Log.debug("invalidate ",_clusterId);
1061                 // Notify listeners and unbind values
1062                 if (isNotAvailable())
1063                     throw new IllegalStateException();
1064 
1065                 while (_attributes!=null && _attributes.size()>0)
1066                 {
1067                     ArrayList keys;
1068                     synchronized (Session.this)
1069                     {
1070                         keys=new ArrayList(_attributes.keySet());
1071                     }
1072 
1073                     Iterator iter=keys.iterator();
1074                     while (iter.hasNext())
1075                     {
1076                         String key=(String)iter.next();
1077 
1078                         Object value;
1079                         synchronized (Session.this)
1080                         {
1081                             value=_attributes.remove(key);
1082                         }
1083                         unbindValue(key,value);
1084 
1085                         if (_sessionAttributeListeners!=null)
1086                         {
1087                             HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,key,value);
1088 
1089                             for (int i=0; i<LazyList.size(_sessionAttributeListeners); i++)
1090                                 ((HttpSessionAttributeListener)LazyList.get(_sessionAttributeListeners,i)).attributeRemoved(event);
1091                         }
1092                     }
1093                 }
1094             }
1095             finally
1096             {
1097                 // mark as invalid
1098                 _invalid=true;
1099             }
1100         }
1101 
1102         /* ------------------------------------------------------------- */
1103         public boolean isIdChanged()
1104         {
1105             return _idChanged;
1106         }
1107 
1108         /* ------------------------------------------------------------- */
1109         public boolean isNew() throws IllegalStateException
1110         {
1111             if (isNotAvailable())
1112                 throw new IllegalStateException();
1113             return _newSession;
1114         }
1115 
1116         /* ------------------------------------------------------------- */
1117         /**
1118          * @deprecated As of Version 2.2, this method is replaced by
1119          *             {@link #setAttribute}
1120          */
1121         @Deprecated
1122         public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException
1123         {
1124             setAttribute(name,value);
1125         }
1126 
1127         /* ------------------------------------------------------------ */
1128         public void removeAttribute(String name)
1129         {
1130             Object old;
1131             synchronized(Session.this)
1132             {
1133                 if (isNotAvailable())
1134                     throw new IllegalStateException();
1135                 if (_attributes==null)
1136                     return;
1137 
1138                 old=_attributes.remove(name);
1139             }
1140 
1141             if (old!=null)
1142             {
1143                 unbindValue(name,old);
1144                 if (_sessionAttributeListeners!=null)
1145                 {
1146                     HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,name,old);
1147 
1148                     for (int i=0; i<LazyList.size(_sessionAttributeListeners); i++)
1149                         ((HttpSessionAttributeListener)LazyList.get(_sessionAttributeListeners,i)).attributeRemoved(event);
1150                 }
1151             }
1152 
1153         }
1154 
1155         /* ------------------------------------------------------------- */
1156         /**
1157          * @deprecated As of Version 2.2, this method is replaced by
1158          *             {@link #removeAttribute}
1159          */
1160         @Deprecated
1161         public void removeValue(java.lang.String name) throws IllegalStateException
1162         {
1163             removeAttribute(name);
1164         }
1165 
1166         /* ------------------------------------------------------------ */
1167         public void setAttribute(String name, Object value)
1168         {
1169             Object old_value=null;
1170             synchronized (Session.this)
1171             {
1172                 if (value==null)
1173                 {
1174                     removeAttribute(name);
1175                     return;
1176                 }
1177 
1178                 if (isNotAvailable())
1179                     throw new IllegalStateException();
1180                 old_value=_attributes.put(name,value);
1181             }
1182             
1183             if (old_value==null || !value.equals(old_value))
1184             {
1185                 unbindValue(name,old_value);
1186                 bindValue(name,value);
1187 
1188                 if (_sessionAttributeListeners!=null)
1189                 {
1190                     HttpSessionBindingEvent event=new HttpSessionBindingEvent(this,name,old_value==null?value:old_value);
1191 
1192                     for (int i=0; i<LazyList.size(_sessionAttributeListeners); i++)
1193                     {
1194                         HttpSessionAttributeListener l=(HttpSessionAttributeListener)LazyList.get(_sessionAttributeListeners,i);
1195 
1196                         if (old_value==null)
1197                             l.attributeAdded(event);
1198                         else
1199                             l.attributeReplaced(event);
1200                     }
1201                 }
1202             }
1203         }
1204 
1205         /* ------------------------------------------------------------- */
1206         public void setIdChanged(boolean changed)
1207         {
1208             _idChanged=changed;
1209         }
1210 
1211         /* ------------------------------------------------------------- */
1212         public void setMaxInactiveInterval(int secs)
1213         {
1214             _maxIdleMs=(long)secs*1000;
1215         }
1216 
1217         /* ------------------------------------------------------------- */
1218         @Override
1219         public String toString()
1220         {
1221             return this.getClass().getName()+":"+getId()+"@"+hashCode();
1222         }
1223 
1224         /* ------------------------------------------------------------- */
1225         /** If value implements HttpSessionBindingListener, call valueBound() */
1226         protected void bindValue(java.lang.String name, Object value)
1227         {
1228             if (value!=null&&value instanceof HttpSessionBindingListener)
1229                 ((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this,name));
1230         }
1231 
1232         /* ------------------------------------------------------------ */
1233         protected boolean isValid()
1234         {
1235             return !_invalid;
1236         }
1237 
1238         /* ------------------------------------------------------------- */
1239         protected void cookieSet()
1240         {
1241             _cookieSet=_accessed;
1242         }
1243 
1244         /* ------------------------------------------------------------- */
1245         /** If value implements HttpSessionBindingListener, call valueUnbound() */
1246         protected void unbindValue(java.lang.String name, Object value)
1247         {
1248             if (value!=null&&value instanceof HttpSessionBindingListener)
1249                 ((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this,name));
1250         }
1251 
1252         /* ------------------------------------------------------------- */
1253         protected void willPassivate()
1254         {
1255             synchronized(Session.this)
1256             {
1257                 HttpSessionEvent event = new HttpSessionEvent(this);
1258                 for (Iterator iter = _attributes.values().iterator(); iter.hasNext();)
1259                 {
1260                     Object value = iter.next();
1261                     if (value instanceof HttpSessionActivationListener)
1262                     {
1263                         HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
1264                         listener.sessionWillPassivate(event);
1265                     }
1266                 }
1267             }
1268         }
1269 
1270         /* ------------------------------------------------------------- */
1271         protected void didActivate()
1272         {
1273             synchronized(Session.this)
1274             {
1275                 HttpSessionEvent event = new HttpSessionEvent(this);
1276                 for (Iterator iter = _attributes.values().iterator(); iter.hasNext();)
1277                 {
1278                     Object value = iter.next();
1279                     if (value instanceof HttpSessionActivationListener)
1280                     {
1281                         HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
1282                         listener.sessionDidActivate(event);
1283                     }
1284                 }
1285             }
1286         }
1287     }
1288 }