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