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.util.Collections;
19  import java.util.Enumeration;
20  import java.util.EventListener;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.concurrent.CopyOnWriteArrayList;
24  
25  import javax.servlet.ServletRequest;
26  import javax.servlet.http.HttpServletRequest;
27  import javax.servlet.http.HttpSession;
28  import javax.servlet.http.HttpSessionAttributeListener;
29  import javax.servlet.http.HttpSessionBindingEvent;
30  import javax.servlet.http.HttpSessionContext;
31  import javax.servlet.http.HttpSessionEvent;
32  import javax.servlet.http.HttpSessionListener;
33  
34  import org.eclipse.jetty.http.HttpCookie;
35  import org.eclipse.jetty.server.AbstractConnector;
36  import org.eclipse.jetty.server.Request;
37  import org.eclipse.jetty.server.Server;
38  import org.eclipse.jetty.server.SessionIdManager;
39  import org.eclipse.jetty.server.SessionManager;
40  import org.eclipse.jetty.server.handler.ContextHandler;
41  import org.eclipse.jetty.util.component.AbstractLifeCycle;
42  import org.eclipse.jetty.util.log.Logger;
43  import org.eclipse.jetty.util.statistic.CounterStatistic;
44  import org.eclipse.jetty.util.statistic.SampleStatistic;
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 specialised version of the Session inner class that provides an attribute
53   * Map.
54   * <p>
55   */
56  @SuppressWarnings("deprecation")
57  public abstract class AbstractSessionManager extends AbstractLifeCycle implements SessionManager
58  {
59      final static Logger __log = SessionHandler.__log;
60      
61      /* ------------------------------------------------------------ */
62      public final static int __distantFuture=60*60*24*7*52*20;
63  
64      static final HttpSessionContext __nullSessionContext=new HttpSessionContext()
65      {
66          public HttpSession getSession(String sessionId)
67          {
68              return null;
69          }
70          
71          @SuppressWarnings({ "rawtypes", "unchecked" })
72          public Enumeration getIds()
73          {
74              return Collections.enumeration(Collections.EMPTY_LIST);
75          }
76      };
77      
78      private boolean _usingCookies=true;
79  
80      /* ------------------------------------------------------------ */
81      // Setting of max inactive interval for new sessions
82      // -1 means no timeout
83      protected int _dftMaxIdleSecs=-1;
84      protected SessionHandler _sessionHandler;
85      protected boolean _httpOnly=false;
86      protected SessionIdManager _sessionIdManager;
87      protected boolean _secureCookies=false;
88      protected final List<HttpSessionAttributeListener> _sessionAttributeListeners = new CopyOnWriteArrayList<HttpSessionAttributeListener>();
89      protected final List<HttpSessionListener> _sessionListeners= new CopyOnWriteArrayList<HttpSessionListener>();
90  
91      protected ClassLoader _loader;
92      protected ContextHandler.Context _context;
93      protected String _sessionCookie=__DefaultSessionCookie;
94      protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
95      protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"=";
96      protected String _sessionDomain;
97      protected String _sessionPath;
98      protected int _maxCookieAge=-1;
99      protected int _refreshCookieAge;
100     protected boolean _nodeIdInSessionId;
101     protected boolean _checkingRemoteSessionIdEncoding;
102 
103     protected final CounterStatistic _sessionsStats = new CounterStatistic();
104     protected final SampleStatistic _sessionTimeStats = new SampleStatistic();
105     
106     /* ------------------------------------------------------------ */
107     public AbstractSessionManager()
108     {
109     }
110 
111     /* ------------------------------------------------------------ */
112     public ContextHandler.Context getContext()
113     {
114         return _context;
115     }
116 
117     /* ------------------------------------------------------------ */
118     public ContextHandler getContextHandler()
119     {
120         return _context.getContextHandler();
121     }
122     
123     /* ------------------------------------------------------------ */
124     public HttpCookie access(HttpSession session,boolean secure)
125     {
126         long now=System.currentTimeMillis();
127 
128         AbstractSession s = ((SessionIf)session).getSession();
129 
130         if (s.access(now))
131         {
132             // Do we need to refresh the cookie?
133             if (isUsingCookies() &&
134                     (s.isIdChanged() ||
135                             (getMaxCookieAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
136                     )
137             )
138             {
139                 HttpCookie cookie=getSessionCookie(session,_context==null?"/":(_context.getContextPath()),secure);
140                 s.cookieSet();
141                 s.setIdChanged(false);
142                 return cookie;
143             }
144         }
145         return null;
146     }
147 
148     /* ------------------------------------------------------------ */
149     public void addEventListener(EventListener listener)
150     {
151         if (listener instanceof HttpSessionAttributeListener)
152             _sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
153         if (listener instanceof HttpSessionListener)
154             _sessionListeners.add((HttpSessionListener)listener);
155     }
156 
157     /* ------------------------------------------------------------ */
158     public void clearEventListeners()
159     {
160         _sessionAttributeListeners.clear();
161         _sessionListeners.clear();
162     }
163 
164     /* ------------------------------------------------------------ */
165     public void complete(HttpSession session)
166     {
167         AbstractSession s = ((SessionIf)session).getSession();
168         s.complete();
169     }
170 
171     /* ------------------------------------------------------------ */
172     @Override
173     public void doStart() throws Exception
174     {
175         _context=ContextHandler.getCurrentContext();
176         _loader=Thread.currentThread().getContextClassLoader();
177 
178         if (_sessionIdManager==null)
179         {
180             final Server server=getSessionHandler().getServer();
181             synchronized (server)
182             {
183                 _sessionIdManager=server.getSessionIdManager();
184                 if (_sessionIdManager==null)
185                 {
186                     _sessionIdManager=new HashSessionIdManager();
187                     server.setSessionIdManager(_sessionIdManager);
188                 }
189             }
190         }
191         if (!_sessionIdManager.isStarted())
192             _sessionIdManager.start();
193 
194         // Look for a session cookie name
195         if (_context!=null)
196         {
197             String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty);
198             if (tmp!=null)
199                 _sessionCookie=tmp;
200 
201             tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty);
202             if (tmp!=null)
203                 setSessionIdPathParameterName(tmp);
204 
205             // set up the max session cookie age if it isn't already
206             if (_maxCookieAge==-1)
207             {
208                 tmp=_context.getInitParameter(SessionManager.__MaxAgeProperty);
209                 if (tmp!=null)
210                     _maxCookieAge=Integer.parseInt(tmp.trim());
211             }
212 
213             // set up the session domain if it isn't already
214             if (_sessionDomain==null)
215                 _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty);
216 
217             // set up the sessionPath if it isn't already
218             if (_sessionPath==null)
219                 _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
220             
221             tmp=_context.getInitParameter(SessionManager.__CheckRemoteSessionEncoding);
222             if (tmp!=null)
223                 _checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
224         }
225 
226         super.doStart();
227     }
228 
229     /* ------------------------------------------------------------ */
230     @Override
231     public void doStop() throws Exception
232     {
233         super.doStop();
234 
235         invalidateSessions();
236 
237         _loader=null;
238     }
239 
240     /* ------------------------------------------------------------ */
241     /**
242      * @return Returns the httpOnly.
243      */
244     public boolean getHttpOnly()
245     {
246         return _httpOnly;
247     }
248 
249     /* ------------------------------------------------------------ */
250     public HttpSession getHttpSession(String nodeId)
251     {
252         String cluster_id = getSessionIdManager().getClusterId(nodeId);
253 
254         AbstractSession session = getSession(cluster_id);
255         if (session!=null && !session.getNodeId().equals(nodeId))
256             session.setIdChanged(true);
257         return session;
258     }
259 
260     /* ------------------------------------------------------------ */
261     /**
262      * @return Returns the metaManager used for cross context session management
263      * @deprecated Use {@link #getSessionIdManager()}
264      */
265     public SessionIdManager getIdManager()
266     {
267         return getSessionIdManager();
268     }
269     
270     /* ------------------------------------------------------------ */
271     /**
272      * @return Returns the SessionIdManager used for cross context session management
273      */
274     public SessionIdManager getSessionIdManager()
275     {
276         return _sessionIdManager;
277     }
278 
279     /* ------------------------------------------------------------ */
280     public int getMaxCookieAge()
281     {
282         return _maxCookieAge;
283     }
284 
285     /* ------------------------------------------------------------ */
286     /**
287      * @return seconds
288      */
289     public int getMaxInactiveInterval()
290     {
291         return _dftMaxIdleSecs;
292     }
293     
294     /* ------------------------------------------------------------ */
295     /**
296      * @see #getSessionsMax()
297      */
298     @Deprecated
299     public int getMaxSessions()
300     {
301         return getSessionsMax();
302     }
303 
304     /* ------------------------------------------------------------ */
305     /**
306      * @return maximum number of sessions
307      */
308     public int getSessionsMax()
309     {
310         return (int)_sessionsStats.getMax();
311     }
312 
313     /* ------------------------------------------------------------ */
314     /**
315      * @return total number of sessions
316      */
317     public int getSessionsTotal()
318     {
319         return (int)_sessionsStats.getTotal();
320     }
321 
322     /* ------------------------------------------------------------ */
323     /**
324      * @deprecated use {@link #getSessionIdManager()}
325      */
326     @Deprecated
327     public SessionIdManager getMetaManager()
328     {
329         return getSessionIdManager();
330     }
331 
332     /* ------------------------------------------------------------ */
333     /**
334      * @deprecated always returns 0. no replacement available.
335      */
336     @Deprecated
337     public int getMinSessions()
338     {
339         return 0;
340     }
341 
342     /* ------------------------------------------------------------ */
343     public int getRefreshCookieAge()
344     {
345         return _refreshCookieAge;
346     }
347 
348 
349     /* ------------------------------------------------------------ */
350     /**
351      * @return Returns the secureCookies.
352      */
353     public boolean getSecureCookies()
354     {
355         return _secureCookies;
356     }
357 
358     /* ------------------------------------------------------------ */
359     public String getSessionCookie()
360     {
361         return _sessionCookie;
362     }
363 
364     /* ------------------------------------------------------------ */
365     public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
366     {
367         if (isUsingCookies())
368         {
369             String sessionPath = (_sessionPath==null) ? contextPath : _sessionPath;
370             sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
371             String id = getNodeId(session);
372             HttpCookie cookie=new HttpCookie(
373                     _sessionCookie,
374                     id,
375                     _sessionDomain,
376                     sessionPath,
377                     getMaxCookieAge(),
378                     getHttpOnly(),
379                     requestIsSecure&&getSecureCookies());      
380                     
381             return cookie;
382         }
383         return null;
384     }
385 
386     public String getSessionDomain()
387     {
388         return _sessionDomain;
389     }
390 
391     /* ------------------------------------------------------------ */
392     /**
393      * @return Returns the sessionHandler.
394      */
395     public SessionHandler getSessionHandler()
396     {
397         return _sessionHandler;
398     }
399 
400     /* ------------------------------------------------------------ */
401     /**
402      * @deprecated  Need to review if it is needed.
403      */
404     @SuppressWarnings("rawtypes")
405     public Map getSessionMap()
406     {
407         throw new UnsupportedOperationException();
408     }
409 
410     /* ------------------------------------------------------------ */
411     public String getSessionPath()
412     {
413         return _sessionPath;
414     }
415 
416     /* ------------------------------------------------------------ */
417     public int getSessions()
418     {
419         return (int)_sessionsStats.getCurrent();
420     }
421 
422     /* ------------------------------------------------------------ */
423     public String getSessionIdPathParameterName()
424     {
425         return _sessionIdPathParameterName;
426     }
427 
428     /* ------------------------------------------------------------ */
429     public String getSessionIdPathParameterNamePrefix()
430     {
431         return _sessionIdPathParameterNamePrefix;
432     }
433 
434     /* ------------------------------------------------------------ */
435     /**
436      * @return Returns the usingCookies.
437      */
438     public boolean isUsingCookies()
439     {
440         return _usingCookies;
441     }
442 
443     /* ------------------------------------------------------------ */
444     public boolean isValid(HttpSession session)
445     {
446         AbstractSession s = ((SessionIf)session).getSession();
447         return s.isValid();
448     }
449 
450     /* ------------------------------------------------------------ */
451     public String getClusterId(HttpSession session)
452     {
453         AbstractSession s = ((SessionIf)session).getSession();
454         return s.getClusterId();
455     }
456 
457     /* ------------------------------------------------------------ */
458     public String getNodeId(HttpSession session)
459     {
460         AbstractSession s = ((SessionIf)session).getSession();
461         return s.getNodeId();
462     }
463 
464     /* ------------------------------------------------------------ */
465     /**
466      * Create a new HttpSession for a request
467      */
468     public HttpSession newHttpSession(HttpServletRequest request)
469     {
470         AbstractSession session=newSession(request);
471         session.setMaxInactiveInterval(_dftMaxIdleSecs);
472         addSession(session,true);
473         return session;
474     }
475 
476     /* ------------------------------------------------------------ */
477     public void removeEventListener(EventListener listener)
478     {
479         if (listener instanceof HttpSessionAttributeListener)
480             _sessionAttributeListeners.remove(listener);
481         if (listener instanceof HttpSessionListener)
482             _sessionListeners.remove(listener);
483     }
484     
485     /* ------------------------------------------------------------ */
486     /**
487      * @see #statsReset()
488      */
489     @Deprecated
490     public void resetStats()
491     {
492         statsReset();
493     }
494 
495     /* ------------------------------------------------------------ */
496     /**
497      * Reset statistics values
498      */
499     public void statsReset()
500     {
501         _sessionsStats.reset(getSessions());
502         _sessionTimeStats.reset();
503     }
504 
505     /* ------------------------------------------------------------ */
506     /**
507      * @param httpOnly
508      *            The httpOnly to set.
509      */
510     public void setHttpOnly(boolean httpOnly)
511     {
512         _httpOnly=httpOnly;
513     }
514 
515     /* ------------------------------------------------------------ */
516     /**
517      * @param metaManager The metaManager used for cross context session management.
518      * @deprecated use {@link #setSessionIdManager(SessionIdManager)}
519      */
520     public void setIdManager(SessionIdManager metaManager)
521     {
522         setSessionIdManager(metaManager);
523     }
524     
525     /* ------------------------------------------------------------ */
526     /**
527      * @param metaManager The metaManager used for cross context session management.
528      */
529     public void setSessionIdManager(SessionIdManager metaManager)
530     {
531         _sessionIdManager=metaManager;
532     }
533 
534     /* ------------------------------------------------------------ */
535     public void setMaxCookieAge(int maxCookieAgeInSeconds)
536     {
537         _maxCookieAge=maxCookieAgeInSeconds;
538 
539         if (_maxCookieAge>0 && _refreshCookieAge==0)
540             _refreshCookieAge=_maxCookieAge/3;
541 
542     }
543 
544     /* ------------------------------------------------------------ */
545     /**
546      * @param seconds
547      */
548     public void setMaxInactiveInterval(int seconds)
549     {
550         _dftMaxIdleSecs=seconds;
551     }
552 
553     /* ------------------------------------------------------------ */
554     /**
555      * @deprecated use {@link #setSessionIdManager(SessionIdManager)}
556      */
557     @Deprecated
558     public void setMetaManager(SessionIdManager metaManager)
559     {
560         setSessionIdManager(metaManager);
561     }
562 
563     /* ------------------------------------------------------------ */
564     public void setRefreshCookieAge(int ageInSeconds)
565     {
566         _refreshCookieAge=ageInSeconds;
567     }
568 
569 
570     /* ------------------------------------------------------------ */
571     /**
572      * Set if the session manager should use SecureCookies.
573      * A secure cookie will only be sent by a browser on a secure (https) connection to 
574      * avoid the concern of cookies being intercepted on non secure channels.
575      * For the cookie to be issued as secure, the {@link ServletRequest#isSecure()} method must return true.
576      * If SSL offload is used, then the {@link AbstractConnector#customize(org.eclipse.jetty.io.EndPoint, Request)}
577      * method can be used to force the request to be https, or the {@link AbstractConnector#setForwarded(boolean)}
578      * can be set to true, so that the X-Forwarded-Proto header is respected.
579      * <p>
580      * If secure session cookies are used, then a session may not be shared between http and https requests.
581      * 
582      * @param secureCookies If true, use secure cookies.
583      */
584     public void setSecureCookies(boolean secureCookies)
585     {
586         _secureCookies=secureCookies;
587     }
588 
589     public void setSessionCookie(String cookieName)
590     {
591         _sessionCookie=cookieName;
592     }
593 
594     public void setSessionDomain(String domain)
595     {
596         _sessionDomain=domain;
597     }
598 
599     /* ------------------------------------------------------------ */
600     /**
601      * @param sessionHandler
602      *            The sessionHandler to set.
603      */
604     public void setSessionHandler(SessionHandler sessionHandler)
605     {
606         _sessionHandler=sessionHandler;
607     }
608 
609     /* ------------------------------------------------------------ */
610     /**
611      * @see org.eclipse.jetty.server.SessionManager#setSessionPath(java.lang.String)
612      */
613     public void setSessionPath(String path)
614     {
615         _sessionPath=path;
616     }
617 
618     /* ------------------------------------------------------------ */
619     public void setSessionIdPathParameterName(String param)
620     {
621         _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
622         _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
623     }
624     /* ------------------------------------------------------------ */
625     /**
626      * @param usingCookies
627      *            The usingCookies to set.
628      */
629     public void setUsingCookies(boolean usingCookies)
630     {
631         _usingCookies=usingCookies;
632     }
633 
634 
635     protected abstract void addSession(AbstractSession session);
636 
637     /* ------------------------------------------------------------ */
638     /**
639      * Add the session Registers the session with this manager and registers the
640      * session ID with the sessionIDManager;
641      */
642     protected void addSession(AbstractSession session, boolean created)
643     {
644         synchronized (_sessionIdManager)
645         {
646             _sessionIdManager.addSession(session);
647             addSession(session);
648         }
649 
650         if (created)
651         {
652             _sessionsStats.increment();
653             if (_sessionListeners!=null)
654             {
655                 HttpSessionEvent event=new HttpSessionEvent(session);
656                 for (HttpSessionListener listener : _sessionListeners)
657                     listener.sessionCreated(event);
658             }
659         }
660     }
661 
662     /* ------------------------------------------------------------ */
663     /**
664      * Get a known existing session
665      * @param idInCluster The session ID in the cluster, stripped of any worker name.
666      * @return A Session or null if none exists.
667      */
668     public abstract AbstractSession getSession(String idInCluster);
669 
670     protected abstract void invalidateSessions() throws Exception;
671 
672 
673     /* ------------------------------------------------------------ */
674     /**
675      * Create a new session instance
676      * @param request
677      * @return the new session
678      */
679     protected abstract AbstractSession newSession(HttpServletRequest request);
680 
681 
682     /* ------------------------------------------------------------ */
683     /**
684      * @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false.
685      */
686     public boolean isNodeIdInSessionId()
687     {
688         return _nodeIdInSessionId;
689     }
690 
691     /* ------------------------------------------------------------ */
692     /**
693      * @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.
694      */
695     public void setNodeIdInSessionId(boolean nodeIdInSessionId)
696     {
697         _nodeIdInSessionId=nodeIdInSessionId;
698     }
699 
700     /* ------------------------------------------------------------ */
701     /** Remove session from manager
702      * @param session The session to remove
703      * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
704      * {@link SessionIdManager#invalidateAll(String)} should be called.
705      */
706     public void removeSession(HttpSession session, boolean invalidate)
707     {
708         AbstractSession s = ((SessionIf)session).getSession();
709         removeSession(s,invalidate);
710     }
711 
712     /* ------------------------------------------------------------ */
713     /** Remove session from manager
714      * @param session The session to remove
715      * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
716      * {@link SessionIdManager#invalidateAll(String)} should be called.
717      */
718     public void removeSession(AbstractSession session, boolean invalidate)
719     {
720         // Remove session from context and global maps
721         boolean removed = removeSession(session.getClusterId());
722         
723         if (removed)
724         {
725             _sessionsStats.decrement();
726             _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0));
727             
728             // Remove session from all context and global id maps
729             _sessionIdManager.removeSession(session);
730             if (invalidate)
731                 _sessionIdManager.invalidateAll(session.getClusterId());
732             
733             if (invalidate && _sessionListeners!=null)
734             {
735                 HttpSessionEvent event=new HttpSessionEvent(session);
736                 for (HttpSessionListener listener : _sessionListeners)
737                     listener.sessionDestroyed(event);
738             }
739         }
740     }
741 
742     /* ------------------------------------------------------------ */
743     protected abstract boolean removeSession(String idInCluster);
744     
745     /* ------------------------------------------------------------ */
746     /**
747      * @return maximum amount of time session remained valid
748      */
749     public long getSessionTimeMax()
750     {
751         return _sessionTimeStats.getMax();
752     }
753 
754     /* ------------------------------------------------------------ */
755     /**
756      * @return total amount of time all sessions remained valid
757      */
758     public long getSessionTimeTotal()
759     {
760         return _sessionTimeStats.getTotal();
761     }
762     
763     /* ------------------------------------------------------------ */
764     /**
765      * @return mean amount of time session remained valid
766      */
767     public double getSessionTimeMean()
768     {
769         return _sessionTimeStats.getMean();
770     }
771     
772     /* ------------------------------------------------------------ */
773     /**
774      * @return standard deviation of amount of time session remained valid
775      */
776     public double getSessionTimeStdDev()
777     {
778         return _sessionTimeStats.getStdDev();
779     }
780 
781     /* ------------------------------------------------------------ */
782     /**
783      * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding()
784      */
785     public boolean isCheckingRemoteSessionIdEncoding()
786     {
787         return _checkingRemoteSessionIdEncoding;
788     }
789 
790     /* ------------------------------------------------------------ */
791     /**
792      * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean)
793      */
794     public void setCheckingRemoteSessionIdEncoding(boolean remote)
795     {
796         _checkingRemoteSessionIdEncoding=remote;
797     }
798     
799     /* ------------------------------------------------------------ */
800     /* ------------------------------------------------------------ */
801     /* ------------------------------------------------------------ */
802     /**
803      * Interface that any session wrapper should implement so that
804      * SessionManager may access the Jetty session implementation.
805      *
806      */
807     public interface SessionIf extends HttpSession
808     {
809         public AbstractSession getSession();
810     }
811 
812     public void doSessionAttributeListeners(AbstractSession session, String name, Object old, Object value)
813     {
814         if (!_sessionAttributeListeners.isEmpty())
815         {
816             HttpSessionBindingEvent event=new HttpSessionBindingEvent(session,name,old==null?value:old);
817 
818             for (HttpSessionAttributeListener l : _sessionAttributeListeners)
819             {
820                 if (old==null)
821                     l.attributeAdded(event);
822                 else if (value==null)
823                     l.attributeRemoved(event);
824                 else
825                     l.attributeReplaced(event);
826             }
827         }
828     }
829 }