KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > web > tomcat > tc6 > session > ClusteredSession


1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */

22 package org.jboss.web.tomcat.tc6.session;
23
24 import java.beans.PropertyChangeSupport JavaDoc;
25 import java.io.Externalizable JavaDoc;
26 import java.io.IOException JavaDoc;
27 import java.io.ObjectInput JavaDoc;
28 import java.io.ObjectOutput JavaDoc;
29 import java.io.Serializable JavaDoc;
30 import java.security.Principal JavaDoc;
31 import java.util.Collections JavaDoc;
32 import java.util.Enumeration JavaDoc;
33 import java.util.HashMap JavaDoc;
34 import java.util.HashSet JavaDoc;
35 import java.util.Map JavaDoc;
36 import java.util.Set JavaDoc;
37
38 import javax.servlet.http.HttpSessionActivationListener JavaDoc;
39 import javax.servlet.http.HttpSessionAttributeListener JavaDoc;
40 import javax.servlet.http.HttpSessionBindingEvent JavaDoc;
41 import javax.servlet.http.HttpSessionBindingListener JavaDoc;
42 import javax.servlet.http.HttpSessionEvent JavaDoc;
43 import javax.servlet.http.HttpSessionListener JavaDoc;
44
45 import org.apache.catalina.Context;
46 import org.apache.catalina.Globals;
47 import org.apache.catalina.Session;
48 import org.apache.catalina.session.StandardSession;
49 import org.apache.catalina.util.Enumerator;
50 import org.apache.catalina.util.StringManager;
51 import org.jboss.logging.Logger;
52
53 /**
54  * Abstract base class for session clustering based on StandardSession. Different session
55  * replication strategy can be implemented such as session- or attribute-based ones.
56  *
57  * @author Ben Wang
58  * @author Brian Stansberry
59  *
60  * @version $Revision: 58585 $
61  */

62 abstract class ClusteredSession
63    extends StandardSession
64    implements Externalizable JavaDoc
65 {
66    private static final long serialVersionUID = -758573655613558722L;
67    protected static Logger log = Logger.getLogger(ClusteredSession.class);
68
69    // ----------------------------------------------------- Instance Variables
70
/**
71     * Descriptive information describing this Session implementation.
72     */

73    protected static final String JavaDoc info = "ClusteredSession/1.0";
74
75    /**
76     * Set of attribute names which are not allowed to be replicated/persisted.
77     */

78    protected static final String JavaDoc[] excludedAttributes = {
79        Globals.SUBJECT_ATTR
80    };
81    
82    /**
83     * Set containing all members of {@link #excludedAttributes}.
84     */

85    protected static final Set JavaDoc replicationExcludes;
86    static
87    {
88       HashSet JavaDoc set = new HashSet JavaDoc();
89       for (int i = 0; i < excludedAttributes.length; i++)
90       {
91          set.add(excludedAttributes[i]);
92       }
93       replicationExcludes = Collections.unmodifiableSet(set);
94    }
95    
96    protected InvalidateSessionPolicy invalidationPolicy;
97
98    /**
99     * If true, means the local in-memory session data contains
100     * changes that have not been published to the distributed cache.
101     *
102     * @deprecated not used
103     */

104    protected transient boolean isSessionModifiedSinceLastSave;
105    
106    /**
107     * If true, means the local in-memory session data contains metadata
108     * changes that have not been published to the distributed cache.
109     */

110    protected transient boolean sessionMetadataDirty;
111    
112    /**
113     * If true, means the local in-memory session data contains attribute
114     * changes that have not been published to the distributed cache.
115     */

116    protected transient boolean sessionAttributesDirty;
117    
118    /**
119     * The last version that was passed to {@link #setOutdatedVersion} or
120     * <code>0</code> if <code>setIsOutdated(false)</code> was subsequently called.
121     */

122    protected transient int outdatedVersion;
123    
124    /**
125     * The last time {@link #setIsOutdated setIsOutdated(true)} was called or
126     * <code>0</code> if <code>setIsOutdated(false)</code> was subsequently called.
127     */

128    protected transient long outdatedTime;
129
130    /**
131     * Version number to track cache invalidation. If any new version number is
132     * greater than this one, it means the data it holds is newer than this one.
133     */

134    protected int version;
135
136    /**
137     * The session's id with any jvmRoute removed.
138     */

139    protected transient String JavaDoc realId;
140    
141    /**
142     * Whether JK is being used, in which case our realId will
143     * not match our id
144     */

145    private transient boolean useJK;
146    
147    /**
148     * Timestamp when we were last replicated.
149     */

150    protected transient long lastReplicated;
151
152    /**
153     * Maximum percentage of the inactive interval this session
154     * should be allowed to go unreplicated if access to the
155     * session doesn't mark it as dirty. Drives the calculation
156     * of maxUnreplicatedInterval.
157     */

158    protected transient int maxUnreplicatedFactor = 80;
159    
160    /**
161     * Maximum number of milliseconds this session
162     * should be allowed to go unreplicated if access to the
163     * session doesn't mark it as dirty.
164     */

165    protected transient long maxUnreplicatedInterval;
166    
167    /**
168     * Whether any of this session's attributes implement
169     * HttpSessionActivationListener.
170     */

171    protected transient Boolean JavaDoc hasActivationListener;
172    
173    /**
174     * Has this session only been accessed once?
175     */

176    protected transient boolean firstAccess;
177
178    /**
179     * The string manager for this package.
180     */

181    protected static StringManager sm =
182       StringManager.getManager(ClusteredSession.class.getPackage().getName());
183
184    /**
185     * Create a new ClusteredSession.
186     *
187     * @param manager the session's manager
188     *
189     * @deprecated use {@link ClusteredSession(AbstractJBossManager, boolean)}
190     */

191    protected ClusteredSession(AbstractJBossManager manager)
192    {
193       this(manager, false);
194    }
195    
196    protected ClusteredSession(AbstractJBossManager manager, boolean useJK)
197    {
198       super(manager);
199       invalidationPolicy = manager.getInvalidateSessionPolicy();
200       this.useJK = useJK;
201       this.firstAccess = true;
202       calcMaxUnreplicatedInterval();
203    }
204
205    /**
206     * Check to see if the session data is still valid. Outdated here means
207     * that the in-memory data is not in sync with one in the data store.
208     *
209     * @return
210     */

211    public boolean isOutdated()
212    {
213       return thisAccessedTime < outdatedTime;
214    }
215
216    /**
217     * Marks this session as outdated or up-to-date vis-a-vis the distributed
218     * cache.
219     *
220     * @param outdated
221     *
222     * @deprecated use {@link #setOutdatedVersion(int)} and {@link #clearOutdated()}
223     */

224    public void setIsOutdated(boolean outdated)
225    {
226       if (outdated)
227          outdatedTime = System.currentTimeMillis();
228       else
229          clearOutdated();
230    }
231    
232    public void setOutdatedVersion(int version)
233    {
234       this.outdatedVersion = version;
235       outdatedTime = System.currentTimeMillis();
236    }
237    
238    public void clearOutdated()
239    {
240       // Only overwrite the access time if access() hasn't been called
241
// since setOutdatedVersion() was called
242
if (outdatedTime > thisAccessedTime)
243       {
244          lastAccessedTime = thisAccessedTime;
245          thisAccessedTime = outdatedTime;
246       }
247       outdatedTime = 0;
248       
249       // Only overwrite the version if the outdated version is greater
250
// Otherwise when we first unmarshal a session that has been
251
// replicated many times, we will reset the version to 0
252
if (outdatedVersion > version)
253          version = outdatedVersion;
254       
255       outdatedVersion = 0;
256    }
257    
258    public void updateAccessTimeFromOutdatedTime()
259    {
260       if (outdatedTime > thisAccessedTime)
261       {
262          lastAccessedTime = thisAccessedTime;
263          thisAccessedTime = outdatedTime;
264       }
265       outdatedTime = 0;
266    }
267
268    /**
269     * Gets the session id with any appended jvmRoute info removed.
270     *
271     * @see #getUseJK()
272     */

273    public String JavaDoc getRealId()
274    {
275       return realId;
276    }
277
278    private void parseRealId(String JavaDoc sessionId)
279    {
280       String JavaDoc newId = null;
281       if (useJK)
282          newId = Util.getRealId(sessionId);
283       else
284          newId = sessionId;
285       
286       // realId is used in a lot of map lookups, so only replace it
287
// if the new id is actually different -- preserve object identity
288
if (!newId.equals(realId))
289          realId = newId;
290    }
291
292    /**
293     * This is called specifically for failover case using mod_jk where the new
294     * session has this node name in there. As a result, it is safe to just
295     * replace the id since the backend store is using the "real" id
296     * without the node name.
297     *
298     * @param id
299     */

300    public void resetIdWithRouteInfo(String JavaDoc id)
301    {
302       this.id = id;
303       parseRealId(id);
304    }
305
306    public boolean getUseJK()
307    {
308       return useJK;
309    }
310
311    /**
312     * Check to see if the input version number is greater than I am. If it is,
313     * it means we will need to invalidate the in-memory cache.
314     * @param version
315     * @return
316     */

317    public boolean isNewData(int version)
318    {
319       return (this.version < version);
320    }
321
322    public int getVersion()
323    {
324       return version;
325    }
326    
327    public void setVersion(int version)
328    {
329       this.version = version;
330    }
331
332    /**
333     * There are couple ways to generate this version number.
334     * But we will stick with the simple one of incrementing for now.
335     *
336     * @return the new version
337     */

338    public int incrementVersion()
339    {
340       return version++;
341    }
342
343    /**
344     * Gets the maximum percentage of the <code>maxInactiveInterval</code>
345     * beyond which a session should be replicated upon access even if it
346     * isn't dirty. Used to ensure that even a read-only session gets
347     * replicated before it expires, so that it isn't removed from other
348     * nodes.
349     *
350     * @return an int between 1 and 100, or -1 if replicating on access is
351     * disabled
352     */

353    public int getMaxUnreplicatedFactor()
354    {
355       return maxUnreplicatedFactor;
356    }
357
358    /**
359     * Sets the maximum percentage of the <code>maxInactiveInterval</code>
360     * beyond which a session should be replicated upon access even if it
361     * isn't dirty. Used to ensure that even a read-only session gets
362     * replicated before it expires, so that it isn't removed from other
363     * nodes.
364     *
365     * @param maxUnreplicatedFactor an int between 1 and 100, or -1 to
366     * disable replicating on access
367     *
368     * @throws IllegalArgumentException if the factor isn't -1 or between
369     * 1 and 100
370     */

371    public void setMaxUnreplicatedFactor(int factor)
372    {
373       if ((factor != -1 && factor < 1) || factor > 100)
374          throw new IllegalArgumentException JavaDoc("Invalid factor " + factor +
375                                    " -- must be between 1 and 100 or -1");
376       this.maxUnreplicatedFactor = factor;
377       calcMaxUnreplicatedInterval();
378    }
379    
380
381    /**
382     * Overrides the superclass to calculate
383     * {@link #getMaxUnreplicatedInterval() maxUnreplicatedInterval}.
384     */

385    public void setMaxInactiveInterval(int interval)
386    {
387       super.setMaxInactiveInterval(interval);
388       calcMaxUnreplicatedInterval();
389       sessionMetadataDirty();
390    }
391
392    /**
393     * Gets the time {@link #updateLastReplicated()} was last called, or
394     * <code>0</code> if it has never been called.
395     */

396    public long getLastReplicated()
397    {
398       return lastReplicated;
399    }
400    
401    /**
402     * Sets the {@link #getLastReplicated() lastReplicated} field to
403     * the current time.
404     */

405    public void updateLastReplicated()
406    {
407       lastReplicated = System.currentTimeMillis();
408    }
409
410    public long getMaxUnreplicatedInterval()
411    {
412       return maxUnreplicatedInterval;
413    }
414    
415    public boolean getExceedsMaxUnreplicatedInterval()
416    {
417       boolean result = false;
418       
419       if (maxUnreplicatedInterval > 0) // -1 means ignore; 0 means expire now
420
{
421          result = ((System.currentTimeMillis() - lastReplicated) >= maxUnreplicatedInterval);
422       }
423       
424       return result;
425    }
426    
427    private void calcMaxUnreplicatedInterval()
428    {
429       if (maxInactiveInterval < 0 || maxUnreplicatedFactor < 0)
430          maxUnreplicatedInterval = -1;
431       else
432          maxUnreplicatedInterval = maxInactiveInterval * maxUnreplicatedFactor / 100;
433    }
434
435    /**
436     * This is called after loading a session to initialize the transient values.
437     *
438     * @param manager
439     */

440    public abstract void initAfterLoad(AbstractJBossManager manager);
441
442    /**
443     * Propogate session to the internal store.
444     */

445    public abstract void processSessionRepl();
446
447    /**
448     * Remove myself from the internal store.
449     */

450    public abstract void removeMyself();
451
452    /**
453     * Remove myself from the <t>local</t> internal store.
454     */

455    public abstract void removeMyselfLocal();
456
457
458    // ----------------------------------------------- Overridden Public Methods
459

460    public void access()
461    {
462       super.access();
463
464       // JBAS-3528. If it's not the first access, make sure
465
// the 'new' flag is correct
466
if (!firstAccess && isNew)
467       {
468          setNew(false);
469       }
470
471       if (invalidationPolicy == InvalidateSessionPolicy.ACCESS)
472       {
473          this.sessionMetadataDirty();
474       }
475    }
476    
477    
478    public void endAccess()
479    {
480       super.endAccess();
481       
482       if (firstAccess)
483       {
484          firstAccess = false;
485          // Tomcat marks the session as non new, but that's not really
486
// accurate per SRV.7.2, as the second request hasn't come in yet
487
// So, we fix that
488
isNew = true;
489       }
490    }
491
492    public Object JavaDoc getAttribute(String JavaDoc name)
493    {
494
495       if (!isValid())
496          throw new IllegalStateException JavaDoc
497             (sm.getString("clusteredSession.getAttribute.ise"));
498
499       return getAttributeInternal(name);
500    }
501
502    public Enumeration JavaDoc getAttributeNames()
503    {
504       if (!isValid())
505          throw new IllegalStateException JavaDoc
506             (sm.getString("clusteredSession.getAttributeNames.ise"));
507
508       return (new Enumerator(getAttributesInternal().keySet(), true));
509    }
510
511    public void setAttribute(String JavaDoc name, Object JavaDoc value)
512    {
513       // Name cannot be null
514
if (name == null)
515          throw new IllegalArgumentException JavaDoc
516             (sm.getString("clusteredSession.setAttribute.namenull"));
517
518       // Null value is the same as removeAttribute()
519
if (value == null)
520       {
521          removeAttribute(name);
522          return;
523       }
524
525       // Validate our current state
526
if (!isValid())
527       {
528          throw new IllegalStateException JavaDoc
529             (sm.getString("clusteredSession.setAttribute.ise"));
530       }
531       
532       if (canAttributeBeReplicated(value) == false)
533       {
534          throw new IllegalArgumentException JavaDoc
535             (sm.getString("clusteredSession.setAttribute.iae"));
536       }
537       // Construct an event with the new value
538
HttpSessionBindingEvent JavaDoc event = null;
539
540       // Call the valueBound() method if necessary
541
if (value instanceof HttpSessionBindingListener JavaDoc)
542       {
543          event = new HttpSessionBindingEvent JavaDoc(getSession(), name, value);
544          try
545          {
546             ((HttpSessionBindingListener JavaDoc) value).valueBound(event);
547          }
548          catch (Throwable JavaDoc t)
549          {
550              manager.getContainer().getLogger().error(sm.getString("standardSession.bindingEvent"), t);
551          }
552       }
553
554       // Replace or add this attribute
555
Object JavaDoc unbound = setInternalAttribute(name, value);
556
557       // Call the valueUnbound() method if necessary
558
if ((unbound != null) && (unbound != value) &&
559          (unbound instanceof HttpSessionBindingListener JavaDoc))
560       {
561          try
562          {
563             ((HttpSessionBindingListener JavaDoc) unbound).valueUnbound
564                (new HttpSessionBindingEvent JavaDoc(getSession(), name));
565          }
566          catch (Throwable JavaDoc t)
567          {
568              manager.getContainer().getLogger().error(sm.getString("standardSession.bindingEvent"), t);
569          }
570       }
571
572       // Notify interested application event listeners
573
Context context = (Context) manager.getContainer();
574       Object JavaDoc listeners[] = context.getApplicationEventListeners();
575       if (listeners == null)
576          return;
577       for (int i = 0; i < listeners.length; i++)
578       {
579          if (!(listeners[i] instanceof HttpSessionAttributeListener JavaDoc))
580             continue;
581          HttpSessionAttributeListener JavaDoc listener =
582             (HttpSessionAttributeListener JavaDoc) listeners[i];
583          try
584          {
585             if (unbound != null)
586             {
587                fireContainerEvent(context,
588                   "beforeSessionAttributeReplaced",
589                   listener);
590                if (event == null)
591                {
592                   event = new HttpSessionBindingEvent JavaDoc
593                      (getSession(), name, unbound);
594                }
595                listener.attributeReplaced(event);
596                fireContainerEvent(context,
597                   "afterSessionAttributeReplaced",
598                   listener);
599             }
600             else
601             {
602                fireContainerEvent(context,
603                   "beforeSessionAttributeAdded",
604                   listener);
605                if (event == null)
606                {
607                   event = new HttpSessionBindingEvent JavaDoc
608                      (getSession(), name, value);
609                }
610                listener.attributeAdded(event);
611                fireContainerEvent(context,
612                   "afterSessionAttributeAdded",
613                   listener);
614             }
615          }
616          catch (Throwable JavaDoc t)
617          {
618             try
619             {
620                if (unbound != null)
621                {
622                   fireContainerEvent(context,
623                      "afterSessionAttributeReplaced",
624                      listener);
625                }
626                else
627                {
628                   fireContainerEvent(context,
629                      "afterSessionAttributeAdded",
630                      listener);
631                }
632             }
633             catch (Exception JavaDoc e)
634             {
635                ;
636             }
637             manager.getContainer().getLogger().error(sm.getString("standardSession.attributeEvent"), t);
638          }
639       }
640    }
641
642
643    /**
644     * Returns whether the attribute's type is one that can be replicated.
645     *
646     * @param attribute the attribute
647     * @return <code>true</code> if <code>attribute</code> is <code>null</code>,
648     * <code>Serializable</code> or an array of primitives.
649     */

650    protected boolean canAttributeBeReplicated(Object JavaDoc attribute)
651    {
652       if (attribute instanceof Serializable JavaDoc || attribute == null)
653          return true;
654       Class JavaDoc clazz = attribute.getClass().getComponentType();
655       return (clazz != null && clazz.isPrimitive());
656    }
657
658    /**
659     * Invalidates this session and unbinds any objects bound to it.
660     * Overridden here to remove across the cluster instead of just expiring.
661     *
662     * @exception IllegalStateException if this method is called on
663     * an invalidated session
664     */

665    public void invalidate()
666    {
667       if (!isValid())
668          throw new IllegalStateException JavaDoc(sm.getString("clusteredSession.invalidate.ise"));
669
670       // Cause this session to expire globally
671
boolean notify = true;
672       boolean localCall = true;
673       boolean localOnly = false;
674       expire(notify, localCall, localOnly);
675    }
676     
677     
678    /**
679     * Overrides the {@link StandardSession#isValid() superclass method}
680     * to call {@ #isValid(boolean) isValid(true)}.
681     */

682    public boolean isValid()
683    {
684       return isValid(true);
685    }
686     
687    /**
688     * Returns whether the current session is still valid, but
689     * only calls {@link #expire(boolean)} for timed-out sessions
690     * if <code>expireIfInvalid</code> is <code>true</code>.
691     *
692     * @param expireIfInvalid <code>true</code> if sessions that have
693     * been timed out should be expired
694     */

695    public boolean isValid(boolean expireIfInvalid)
696    {
697       if (this.expiring)
698       {
699          return true;
700       }
701
702       if (!this.isValid)
703       {
704          return false;
705       }
706
707       if (ACTIVITY_CHECK && accessCount.get() > 0)
708       {
709           return true;
710       }
711
712       if (maxInactiveInterval >= 0)
713       {
714          long timeNow = System.currentTimeMillis();
715          int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
716          if (timeIdle >= maxInactiveInterval)
717          {
718             if (expireIfInvalid)
719                expire(true);
720             else
721                return false;
722          }
723       }
724
725       return (this.isValid);
726        
727    }
728
729    /**
730     * Expires the session, but in such a way that other cluster nodes
731     * are unaware of the expiration.
732     *
733     * @param notify
734     */

735    public void expire(boolean notify)
736    {
737       boolean localCall = true;
738       boolean localOnly = true;
739       expire(notify, localCall, localOnly);
740    }
741
742    /**
743     * Expires the session, notifying listeners and possibly the manager.
744     * <p>
745     * <strong>NOTE:</strong> The manager will only be notified of the expiration
746     * if <code>localCall</code> is <code>true</code>; otherwise it is the
747     * responsibility of the caller to notify the manager that the session is
748     * expired. (In the case of JBossCacheManager, it is the manager itself
749     * that makes such a call, so it of course is aware).
750     * </p>
751     *
752     * @param notify whether servlet spec listeners should be notified
753     * @param localCall <code>true</code> if this call originated due to local
754     * activity (such as a session invalidation in user code
755     * or an expiration by the local background processing
756     * thread); <code>false</code> if the expiration
757     * originated due to some kind of event notification
758     * from the cluster.
759     * @param localOnly <code>true</code> if the expiration should not be
760     * announced to the cluster, <code>false</code> if other
761     * cluster nodes should be made aware of the expiration.
762     * Only meaningful if <code>localCall</code> is
763     * <code>true</code>.
764     */

765    public void expire(boolean notify, boolean localCall, boolean localOnly)
766    {
767       if (log.isDebugEnabled())
768       {
769          log.debug("The session has expired with id: " + id +
770                    " -- is it local? " + localOnly);
771       }
772       
773       // If another thread is already doing this, stop
774
if (expiring)
775          return;
776
777       synchronized (this)
778       {
779          // If we had a race to this sync block, another thread may
780
// have already completed expiration. If so, don't do it again
781
if (!isValid)
782             return;
783
784          if (manager == null)
785             return;
786
787          expiring = true;
788
789          // Notify interested application event listeners
790
// FIXME - Assumes we call listeners in reverse order
791
Context context = (Context) manager.getContainer();
792          Object JavaDoc listeners[] = context.getApplicationLifecycleListeners();
793          if (notify && (listeners != null))
794          {
795             HttpSessionEvent JavaDoc event =
796                new HttpSessionEvent JavaDoc(getSession());
797             for (int i = 0; i < listeners.length; i++)
798             {
799                int j = (listeners.length - 1) - i;
800                if (!(listeners[j] instanceof HttpSessionListener JavaDoc))
801                   continue;
802                HttpSessionListener JavaDoc listener =
803                   (HttpSessionListener JavaDoc) listeners[j];
804                try
805                {
806                   fireContainerEvent(context,
807                      "beforeSessionDestroyed",
808                      listener);
809                   listener.sessionDestroyed(event);
810                   fireContainerEvent(context,
811                      "afterSessionDestroyed",
812                      listener);
813                }
814                catch (Throwable JavaDoc t)
815                {
816                   try
817                   {
818                      fireContainerEvent(context,
819                         "afterSessionDestroyed",
820                         listener);
821                   }
822                   catch (Exception JavaDoc e)
823                   {
824                      ;
825                   }
826                   manager.getContainer().getLogger().error(sm.getString("standardSession.sessionEvent"), t);
827                }
828             }
829          }
830          accessCount = null;
831
832          // Notify interested session event listeners.
833
if (notify)
834          {
835             fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
836          }
837
838          // JBAS-1360 -- Unbind any objects associated with this session
839
String JavaDoc keys[] = keys();
840          for (int i = 0; i < keys.length; i++)
841              removeAttributeInternal(keys[i], localCall, localOnly, notify);
842
843          // Remove this session from our manager's active sessions
844
removeFromManager(localCall, localOnly);
845
846          // We have completed expire of this session
847
setValid(false);
848          expiring = false;
849       }
850
851    }
852    
853    /**
854     * Advise our manager to remove this expired session.
855     *
856     * @param localCall whether this call originated from local activity
857     * or from a remote invalidation. In this default
858     * implementation, this parameter is ignored.
859     * @param localOnly whether the rest of the cluster should be made aware
860     * of the removal
861     */

862    protected void removeFromManager(boolean localCall, boolean localOnly)
863    {
864       if(localOnly)
865       {
866           ((AbstractJBossManager) manager).removeLocal(this);
867       }
868       else
869       {
870          manager.remove(this);
871       }
872    }
873
874    public void passivate()
875    {
876       // Notify interested session event listeners
877
fireSessionEvent(Session.SESSION_PASSIVATED_EVENT, null);
878
879       if (hasActivationListener != Boolean.FALSE)
880       {
881          boolean hasListener = false;
882          
883          // Notify ActivationListeners
884
HttpSessionEvent JavaDoc event = null;
885          String JavaDoc keys[] = keys();
886          Map JavaDoc attrs = getAttributesInternal();
887          for (int i = 0; i < keys.length; i++)
888          {
889             Object JavaDoc attribute = attrs.get(keys[i]);
890             if (attribute instanceof HttpSessionActivationListener JavaDoc)
891             {
892                hasListener = true;
893                
894                if (event == null)
895                   event = new HttpSessionEvent JavaDoc(getSession());
896                try
897                {
898                   ((HttpSessionActivationListener JavaDoc)attribute).sessionWillPassivate(event);
899                }
900                catch (Throwable JavaDoc t)
901                {
902                   manager.getContainer().getLogger().error
903                          (sm.getString("clusteredSession.attributeEvent"), t);
904                }
905             }
906          }
907          
908          hasActivationListener = hasListener ? Boolean.TRUE : Boolean.FALSE;
909       }
910    }
911
912    public void activate()
913    {
914       // Notify interested session event listeners
915
fireSessionEvent(Session.SESSION_ACTIVATED_EVENT, null);
916
917       if (hasActivationListener != Boolean.FALSE)
918       {
919          // Notify ActivationListeners
920

921          boolean hasListener = false;
922          
923          HttpSessionEvent JavaDoc event = null;
924          String JavaDoc keys[] = keys();
925          Map JavaDoc attrs = getAttributesInternal();
926          for (int i = 0; i < keys.length; i++)
927          {
928             Object JavaDoc attribute = attrs.get(keys[i]);
929             if (attribute instanceof HttpSessionActivationListener JavaDoc)
930             {
931                hasListener = true;
932                if (event == null)
933                   event = new HttpSessionEvent JavaDoc(getSession());
934                try
935                {
936                   ((HttpSessionActivationListener JavaDoc)attribute).sessionDidActivate(event);
937                }
938                catch (Throwable JavaDoc t)
939                {
940                   manager.getContainer().getLogger().error
941                          (sm.getString("clusteredSession.attributeEvent"), t);
942                }
943             }
944          }
945          
946          hasActivationListener = hasListener ? Boolean.TRUE : Boolean.FALSE;
947       }
948    }
949
950    // TODO uncomment when work on JBAS-1900 is completed
951
// public void removeNote(String name)
952
// {
953
// // FormAuthenticator removes the username and password because
954
// // it assumes they are not needed if the Principal is cached,
955
// // but they are needed if the session fails over, so ignore
956
// // the removal request.
957
// // TODO discuss this on Tomcat dev list to see if a better
958
// // way of handling this can be found
959
// if (Constants.SESS_USERNAME_NOTE.equals(name)
960
// || Constants.SESS_PASSWORD_NOTE.equals(name))
961
// {
962
// if (log.isDebugEnabled())
963
// {
964
// log.debug("removeNote(): ignoring removal of note " + name);
965
// }
966
// }
967
// else
968
// {
969
// super.removeNote(name);
970
// }
971
//
972
// }
973

974    // TODO uncomment when work on JBAS-1900 is completed
975
// public void setNote(String name, Object value)
976
// {
977
// super.setNote(name, value);
978
//
979
// if (Constants.SESS_USERNAME_NOTE.equals(name)
980
// || Constants.SESS_PASSWORD_NOTE.equals(name))
981
// {
982
// sessionIsDirty();
983
// }
984
// }
985

986    /**
987     * Override the superclass to additionally reset this class' fields.
988     * <p>
989     * <strong>NOTE:</strong> It is not anticipated that this method will be
990     * called on a ClusteredSession, but we are overriding the method to be
991     * thorough.
992     * </p>
993     */

994    public void recycle()
995    {
996       super.recycle();
997       
998       // Fields that the superclass isn't clearing
999
listeners.clear();
1000      support = new PropertyChangeSupport JavaDoc(this);
1001      
1002      invalidationPolicy = InvalidateSessionPolicy.ACCESS;
1003      outdatedTime = 0;
1004      outdatedVersion = 0;
1005      sessionAttributesDirty = false;
1006      sessionMetadataDirty = false;
1007      realId = null;
1008      useJK = false;
1009      version = 0;
1010      hasActivationListener = null;
1011      lastReplicated = 0;
1012      maxUnreplicatedFactor = 80;
1013      calcMaxUnreplicatedInterval();
1014   }
1015   
1016   /**
1017    * Set the creation time for this session. This method is called by the
1018    * Manager when an existing Session instance is reused.
1019    *
1020    * @param time The new creation time
1021    */

1022   public void setCreationTime(long time)
1023   {
1024      super.setCreationTime(time);
1025      sessionMetadataDirty();
1026   }
1027   
1028   /**
1029    * Overrides the superclass method to also set the
1030    * {@link #getRealId() realId} property.
1031    */

1032   public void setId(String JavaDoc id)
1033   {
1034      // Parse the real id first, as super.setId() calls add(),
1035
// which depends on having the real id
1036
parseRealId(id);
1037      super.setId(id);
1038   }
1039
1040   /**
1041    * Set the authenticated Principal that is associated with this Session.
1042    * This provides an <code>Authenticator</code> with a means to cache a
1043    * previously authenticated Principal, and avoid potentially expensive
1044    * <code>Realm.authenticate()</code> calls on every request.
1045    *
1046    * @param principal The new Principal, or <code>null</code> if none
1047    */

1048   public void setPrincipal(Principal JavaDoc principal)
1049   {
1050
1051      Principal JavaDoc oldPrincipal = this.principal;
1052      this.principal = principal;
1053      support.firePropertyChange("principal", oldPrincipal, this.principal);
1054
1055      if ((oldPrincipal != null && !oldPrincipal.equals(principal)) ||
1056         (oldPrincipal == null && principal != null))
1057         sessionMetadataDirty();
1058
1059   }
1060   
1061   public void setNew(boolean isNew)
1062   {
1063      super.setNew(isNew);
1064      // Don't replicate metadata just 'cause its the second request
1065
// The only effect of this is if someone besides a request
1066
// deserializes metadata from the distributed cache, this
1067
// field may be out of date.
1068
// If a request accesses the session, the access() call will
1069
// set isNew=false, so the request will see the correct value
1070
// sessionMetadataDirty();
1071
}
1072   
1073   public void setValid(boolean isValid)
1074   {
1075      super.setValid(isValid);
1076      sessionMetadataDirty();
1077   }
1078
1079   public String JavaDoc toString()
1080   {
1081      StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
1082      buf.append("id: " +id).append(" lastAccessedTime: " +lastAccessedTime).append(
1083              " version: " +version).append(" lastOutdated: " + outdatedTime);
1084
1085      return buf.toString();
1086   }
1087
1088   // --------------------------------------------------------- Externalizable
1089

1090   /**
1091    * Reads all non-transient state from the ObjectOutput <i>except
1092    * the attribute map</i>. Subclasses that wish the attribute map
1093    * to be read should override this method and
1094    * {@link #writeExternal(ObjectOutput) writeExternal()}.
1095    *
1096    * <p>
1097    * This method is deliberately public so it can be used to reset
1098    * the internal state of a session object using serialized
1099    * contents replicated from another JVM via JBossCache.
1100    * </p>
1101    *
1102    * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
1103    */

1104   public void readExternal(ObjectInput JavaDoc in)
1105      throws IOException JavaDoc, ClassNotFoundException JavaDoc
1106   {
1107      synchronized (this)
1108      {
1109         // From StandardSession
1110
id = in.readUTF();
1111         creationTime = in.readLong();
1112         lastAccessedTime = in.readLong();
1113         maxInactiveInterval = in.readInt();
1114         isNew = in.readBoolean();
1115         isValid = in.readBoolean();
1116         thisAccessedTime = in.readLong();
1117         
1118         // From ClusteredSession
1119

1120         invalidationPolicy = InvalidateSessionPolicy.fromInt(in.readInt());
1121         version = in.readInt();
1122   
1123         // Get our id without any jvmRoute appended
1124
parseRealId(id);
1125         
1126         // We no longer know if we have an activationListener
1127
hasActivationListener = null;
1128         
1129         // If the session has been replicated, any subsequent
1130
// access cannot be the first.
1131
this.firstAccess = false;
1132         
1133         // TODO uncomment when work on JBAS-1900 is completed
1134
// // Session notes -- for FORM auth apps, allow replicated session
1135
// // to be used without requiring a new login
1136
// // We use the superclass set/removeNote calls here to bypass
1137
// // the custom logic we've added
1138
// String username = (String) in.readObject();
1139
// if (username != null)
1140
// {
1141
// super.setNote(Constants.SESS_USERNAME_NOTE, username);
1142
// }
1143
// else
1144
// {
1145
// super.removeNote(Constants.SESS_USERNAME_NOTE);
1146
// }
1147
// String password = (String) in.readObject();
1148
// if (password != null)
1149
// {
1150
// super.setNote(Constants.SESS_PASSWORD_NOTE, password);
1151
// }
1152
// else
1153
// {
1154
// super.removeNote(Constants.SESS_PASSWORD_NOTE);
1155
// }
1156
}
1157   }
1158
1159   
1160   /**
1161    * Writes all non-transient state to the ObjectOutput <i>except
1162    * the attribute map</i>. Subclasses that wish the attribute map
1163    * to be written should override this method and append it.
1164    *
1165    * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
1166    */

1167   public void writeExternal(ObjectOutput JavaDoc out)
1168      throws IOException JavaDoc
1169   {
1170      synchronized (this)
1171      {
1172         // From StandardSession
1173
out.writeUTF(id);
1174         out.writeLong(creationTime);
1175         out.writeLong(lastAccessedTime);
1176         out.writeInt(maxInactiveInterval);
1177         out.writeBoolean(isNew);
1178         out.writeBoolean(isValid);
1179         out.writeLong(thisAccessedTime);
1180         
1181         // From ClusteredSession
1182
out.writeInt(invalidationPolicy.ordinal());
1183         out.writeInt(version);
1184         
1185         // TODO uncomment when work on JBAS-1900 is completed
1186
// // Session notes -- for FORM auth apps, allow replicated session
1187
// // to be used without requiring a new login
1188
// String username = (String) getNote(Constants. SESS_USERNAME_NOTE);
1189
// log.debug(Constants.SESS_USERNAME_NOTE + " = " + username);
1190
// out.writeObject(username);
1191
// String password = (String) getNote(Constants.SESS_PASSWORD_NOTE);
1192
// log.debug(Constants.SESS_PASSWORD_NOTE + " = " + password);
1193
// out.writeObject(password);
1194
}
1195   }
1196
1197   // ----------------------------------------------------- Protected Methods
1198

1199   /**
1200    * Removes any attribute whose name is found in {@link #excludedAttributes}
1201    * from <code>attributes</code> and returns a Map of all such attributes.
1202    *
1203    * @param attributes source map from which excluded attributes are to be
1204    * removed.
1205    *
1206    * @return Map that contains any attributes removed from
1207    * <code>attributes</code>, or <code>null</code> if no attributes
1208    * were removed.
1209    */

1210   protected static Map JavaDoc removeExcludedAttributes(Map JavaDoc attributes)
1211   {
1212      Map JavaDoc excluded = null;
1213      for (int i = 0; i < excludedAttributes.length; i++) {
1214         Object JavaDoc attr = attributes.remove(excludedAttributes[i]);
1215         if (attr != null)
1216         {
1217            if (log.isTraceEnabled())
1218            {
1219               log.trace("Excluding attribute " + excludedAttributes[i] +
1220                         " from replication");
1221            }
1222            if (excluded == null)
1223            {
1224               excluded = new HashMap JavaDoc();
1225            }
1226            excluded.put(excludedAttributes[i], attr);
1227         }
1228      }
1229      
1230      return excluded;
1231   }
1232   
1233   /**
1234    * Reads all non-transient state from the ObjectOutput <i>except
1235    * the attribute map</i>. Subclasses that wish the attribute map
1236    * to be read should override this method and
1237    * {@link #writeExternal(ObjectOutput) writeExternal()}.
1238    *
1239    * <p>
1240    * This method is deliberately public so it can be used to reset
1241    * the internal state of a session object using serialized
1242    * contents replicated from another JVM via JBossCache.
1243    * </p>
1244    *
1245    * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
1246    */

1247   protected void update(ClusteredSession replicated)
1248   {
1249      synchronized (this)
1250      {
1251         // From StandardSession
1252
id = replicated.id;
1253         creationTime = replicated.creationTime;
1254         lastAccessedTime = replicated.lastAccessedTime;
1255         maxInactiveInterval = replicated.maxInactiveInterval;
1256         isNew = replicated.isNew;
1257         isValid = replicated.isValid;
1258         thisAccessedTime = replicated.thisAccessedTime;
1259         
1260         // From ClusteredSession
1261
invalidationPolicy = replicated.invalidationPolicy;
1262         version = replicated.version;
1263   
1264         // Get our id without any jvmRoute appended
1265
parseRealId(id);
1266         
1267         // We no longer know if we have an activationListener
1268
hasActivationListener = null;
1269         
1270         // TODO uncomment when work on JBAS-1900 is completed
1271
// // Session notes -- for FORM auth apps, allow replicated session
1272
// // to be used without requiring a new login
1273
// // We use the superclass set/removeNote calls here to bypass
1274
// // the custom logic we've added
1275
// String username = (String) in.readObject();
1276
// if (username != null)
1277
// {
1278
// super.setNote(Constants.SESS_USERNAME_NOTE, username);
1279
// }
1280
// else
1281
// {
1282
// super.removeNote(Constants.SESS_USERNAME_NOTE);
1283
// }
1284
// String password = (String) in.readObject();
1285
// if (password != null)
1286
// {
1287
// super.setNote(Constants.SESS_PASSWORD_NOTE, password);
1288
// }
1289
// else
1290
// {
1291
// super.removeNote(Constants.SESS_PASSWORD_NOTE);
1292
// }
1293
}
1294   }
1295   
1296   
1297   // -------------------------------------- Internal protected method override
1298

1299   /**
1300    * Method inherited from Tomcat. Return zero-length based string if not found.
1301    */

1302   protected String JavaDoc[] keys()
1303   {
1304      return ((String JavaDoc[]) getAttributesInternal().keySet().toArray(EMPTY_ARRAY));
1305   }
1306
1307   /**
1308    * Called by super.removeAttribute().
1309    *
1310    * @param name the attribute name
1311    * @param notify <code>true</code> if listeners should be notified
1312    */

1313   protected void removeAttributeInternal(String JavaDoc name, boolean notify)
1314   {
1315      boolean localCall = true;
1316      boolean localOnly = false;
1317      removeAttributeInternal(name, localCall, localOnly, notify);
1318   }
1319   
1320   /**
1321    * Remove the attribute from the local cache and possibly the distributed
1322    * cache, plus notify any listeners
1323    *
1324    * @param name the attribute name
1325    * @param localCall <code>true</code> if this call originated from local
1326    * activity (e.g. a removeAttribute() in the webapp or a
1327    * local session invalidation/expiration),
1328    * <code>false</code> if it originated due to an remote
1329    * event in the distributed cache.
1330    * @param localOnly <code>true</code> if the removal should not be
1331    * replicated around the cluster
1332    * @param notify <code>true</code> if listeners should be notified
1333    */

1334   protected void removeAttributeInternal(String JavaDoc name,
1335                                          boolean localCall,
1336                                          boolean localOnly,
1337                                          boolean notify)
1338   {
1339      // Remove this attribute from our collection
1340
Object JavaDoc value = removeAttributeInternal(name, localCall, localOnly);
1341
1342      // Do we need to do valueUnbound() and attributeRemoved() notification?
1343
if (!notify || (value == null))
1344      {
1345         return;
1346      }
1347
1348      // Call the valueUnbound() method if necessary
1349
HttpSessionBindingEvent JavaDoc event = null;
1350      if (value instanceof HttpSessionBindingListener JavaDoc)
1351      {
1352         event = new HttpSessionBindingEvent JavaDoc(getSession(), name, value);
1353         ((HttpSessionBindingListener JavaDoc) value).valueUnbound(event);
1354      }
1355
1356      // Notify interested application event listeners
1357
Context context = (Context) manager.getContainer();
1358      Object JavaDoc listeners[] = context.getApplicationEventListeners();
1359      if (listeners == null)
1360         return;
1361      for (int i = 0; i < listeners.length; i++)
1362      {
1363         if (!(listeners[i] instanceof HttpSessionAttributeListener JavaDoc))
1364            continue;
1365         HttpSessionAttributeListener JavaDoc listener =
1366            (HttpSessionAttributeListener JavaDoc) listeners[i];
1367         try
1368         {
1369            fireContainerEvent(context,
1370               "beforeSessionAttributeRemoved",
1371               listener);
1372            if (event == null)
1373            {
1374               event = new HttpSessionBindingEvent JavaDoc
1375                  (getSession(), name, value);
1376            }
1377            listener.attributeRemoved(event);
1378            fireContainerEvent(context,
1379               "afterSessionAttributeRemoved",
1380               listener);
1381         }
1382         catch (Throwable JavaDoc t)
1383         {
1384            try
1385            {
1386               fireContainerEvent(context,
1387                  "afterSessionAttributeRemoved",
1388                  listener);
1389            }
1390            catch (Exception JavaDoc e)
1391            {
1392               ;
1393            }
1394            manager.getContainer().getLogger().error(sm.getString("standardSession.attributeEvent"), t);
1395         }
1396      }
1397
1398   }
1399
1400   /**
1401    * Exists in this class solely to act as an API-compatible bridge to the
1402    * deprecated {@link #removeJBossInternalAttribute(String)}.
1403    * JBossCacheClusteredSession subclasses will override this to call their
1404    * own methods that make use of localCall and localOnly
1405    *
1406    * @param name
1407    * @param localCall
1408    * @param localOnly
1409    * @return
1410    *
1411    * @deprecated will be replaced by removeJBossInternalAttribute(String, boolean, boolean)
1412    */

1413   protected Object JavaDoc removeAttributeInternal(String JavaDoc name,
1414                                            boolean localCall,
1415                                            boolean localOnly)
1416   {
1417      return removeJBossInternalAttribute(name);
1418   }
1419
1420   protected Object JavaDoc getAttributeInternal(String JavaDoc name)
1421   {
1422      return getJBossInternalAttribute(name);
1423   }
1424
1425   protected Map JavaDoc getAttributesInternal()
1426   {
1427      return getJBossInternalAttributes();
1428   }
1429
1430   protected Object JavaDoc setInternalAttribute(String JavaDoc name, Object JavaDoc value)
1431   {
1432      if (value instanceof HttpSessionActivationListener JavaDoc)
1433         hasActivationListener = Boolean.TRUE;
1434      
1435      return setJBossInternalAttribute(name, value);
1436   }
1437
1438   // ------------------------------------------ JBoss internal abstract method
1439

1440   protected abstract Object JavaDoc getJBossInternalAttribute(String JavaDoc name);
1441
1442   /** @deprecated will be replaced by removeJBossInternalAttribute(String, boolean, boolean) */
1443   protected abstract Object JavaDoc removeJBossInternalAttribute(String JavaDoc name);
1444
1445   protected abstract Map JavaDoc getJBossInternalAttributes();
1446
1447   protected abstract Object JavaDoc setJBossInternalAttribute(String JavaDoc name, Object JavaDoc value);
1448
1449   // ------------------------------------------------ Session Package Methods
1450

1451   protected void sessionAttributesDirty()
1452   {
1453      if (!sessionAttributesDirty && log.isTraceEnabled())
1454         log.trace("Marking session attributes dirty" + id);
1455      
1456      sessionAttributesDirty = true;
1457   }
1458   
1459   protected boolean getSessionAttributesDirty()
1460   {
1461      return sessionAttributesDirty;
1462   }
1463   
1464   protected void sessionMetadataDirty()
1465   {
1466      if (!sessionMetadataDirty && !isNew && log.isTraceEnabled())
1467         log.trace("Marking session metadata dirty " + id);
1468      sessionMetadataDirty = true;
1469   }
1470   
1471   protected boolean getSessionMetadataDirty()
1472   {
1473      return sessionMetadataDirty;
1474   }
1475   
1476   /**
1477    * Calls {@link #sessionAttributesDirty()} and
1478    * {@link #sessionMetadataDirty()}.
1479    *
1480    * @deprecated use one of the more fine-grained methods.
1481    */

1482   protected void sessionDirty()
1483   {
1484      sessionAttributesDirty();
1485      sessionMetadataDirty();
1486   }
1487
1488   public boolean isSessionDirty()
1489   {
1490      return sessionAttributesDirty || sessionMetadataDirty;
1491   }
1492   
1493   public boolean getReplicateSessionBody()
1494   {
1495      return sessionMetadataDirty || getExceedsMaxUnreplicatedInterval();
1496   }
1497
1498   protected boolean isGetDirty(Object JavaDoc attribute)
1499   {
1500      boolean result = false;
1501      switch (invalidationPolicy)
1502      {
1503         case SET_AND_GET:
1504            result = true;
1505            break;
1506         case SET_AND_NON_PRIMITIVE_GET:
1507            result = isMutable(attribute);
1508            break;
1509         default:
1510            // result is false
1511
}
1512      return result;
1513   }
1514   
1515   protected boolean isMutable(Object JavaDoc attribute)
1516   {
1517      return attribute != null &&
1518                !(attribute instanceof String JavaDoc ||
1519                  attribute instanceof Number JavaDoc ||
1520                  attribute instanceof Character JavaDoc ||
1521                  attribute instanceof Boolean JavaDoc);
1522   }
1523
1524}
1525
Popular Tags