View Javadoc

1   /*
2    * Copyright 1999,2004 The Apache Software Foundation.
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    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  
18  package org.apache.commons.messagelet.impl;
19  
20  
21  import java.beans.PropertyChangeSupport;
22  import java.io.IOException;
23  import java.io.NotSerializableException;
24  import java.io.ObjectInputStream;
25  import java.io.ObjectOutputStream;
26  import java.io.Serializable;
27  import java.security.Principal;
28  import java.util.ArrayList;
29  import java.util.Enumeration;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  
33  import javax.servlet.ServletContext;
34  import javax.servlet.http.HttpSession;
35  import javax.servlet.http.HttpSessionActivationListener;
36  import javax.servlet.http.HttpSessionBindingEvent;
37  import javax.servlet.http.HttpSessionBindingListener;
38  import javax.servlet.http.HttpSessionContext;
39  import javax.servlet.http.HttpSessionEvent;
40  
41  import org.apache.commons.collections.iterators.IteratorEnumeration;
42  
43  /**
44   * Based on the Catalina StandardSession class.
45   * Standard implementation of the <b>HttpSession</b> interface.  This object is
46   * serializable, so that it can be stored in persistent storage or transferred
47   * to a different JVM for distributable session support.
48   * <p>
49   *
50   * @author Craig R. McClanahan
51   * @author Sean Legassick
52   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
53   * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
54   * @version $Revision: 155459 $ $Date: 2005-02-26 13:24:44 +0000 (Sat, 26 Feb 2005) $
55   */
56  
57  public class HttpSessionImpl implements HttpSession, Serializable {
58  
59  
60  
61      // ----------------------------------------------------- Instance Variables
62  
63  
64      /**
65       * The dummy attribute value serialized when a NotSerializableException is
66       * encountered in <code>writeObject()</code>.
67       */
68      private static final String NOT_SERIALIZED =
69          "___NOT_SERIALIZABLE_EXCEPTION___";
70  
71  
72      /**
73       * The collection of user data attributes associated with this Session.
74       */
75      private HashMap attributes = new HashMap();
76  
77  
78      /**
79       * The authentication type used to authenticate our cached Principal,
80       * if any.  NOTE:  This value is not included in the serialized
81       * version of this object.
82       */
83      private transient String authType = null;
84  
85  
86      /**
87       * The time this session was created, in milliseconds since midnight,
88       * January 1, 1970 GMT.
89       */
90      private long creationTime = 0L;
91  
92  
93      /**
94       * The debugging detail level for this component.  NOTE:  This value
95       * is not included in the serialized version of this object.
96       */
97      private transient int debug = 0;
98  
99  
100     /**
101      * We are currently processing a session expiration, so bypass
102      * certain IllegalStateException tests.  NOTE:  This value is not
103      * included in the serialized version of this object.
104      */
105     private transient boolean expiring = false;
106 
107 
108     /**
109      * The session identifier of this Session.
110      */
111     private String id = null;
112 
113 
114 
115     /**
116      * The last accessed time for this Session.
117      */
118     private long lastAccessedTime = creationTime;
119 
120 
121     /**
122      * The session event listeners for this Session.
123      */
124     private transient ArrayList listeners = new ArrayList();
125 
126 
127 
128     /**
129      * The maximum time interval, in seconds, between client requests before
130      * the servlet container may invalidate this session.  A negative time
131      * indicates that the session should never time out.
132      */
133     private int maxInactiveInterval = -1;
134 
135 
136     /**
137      * Flag indicating whether this session is new or not.
138      */
139     private boolean isNew = false;
140 
141 
142     /**
143      * Flag indicating whether this session is valid or not.
144      */
145     private boolean isValid = false;
146 
147 
148     /**
149      * Internal notes associated with this session by Catalina components
150      * and event listeners.  <b>IMPLEMENTATION NOTE:</b> This object is
151      * <em>not</em> saved and restored across session serializations!
152      */
153     private transient HashMap notes = new HashMap();
154 
155 
156     /**
157      * The authenticated Principal associated with this session, if any.
158      * <b>IMPLEMENTATION NOTE:</b>  This object is <i>not</i> saved and
159      * restored across session serializations!
160      */
161     private transient Principal principal = null;
162 
163 
164     /**
165      * The property change support for this component.  NOTE:  This value
166      * is not included in the serialized version of this object.
167      */
168     private transient PropertyChangeSupport support =
169         new PropertyChangeSupport(this);
170 
171 
172     /**
173      * The current accessed time for this session.
174      */
175     private long thisAccessedTime = creationTime;
176 
177     /**
178      * The ServletContext 
179      */
180     protected ServletContext servletContext;
181 
182     /**
183      * Is this session distributable. If so then 
184      * its values must be Serializable.
185      */
186     private boolean distributable = false;
187     
188     
189     public HttpSessionImpl(ServletContext servletContext) {
190         this.servletContext = servletContext;
191     }
192 
193     // ----------------------------------------------------- Session Properties
194 
195 
196     /**
197      * Return the authentication type used to authenticate our cached
198      * Principal, if any.
199      */
200     public String getAuthType() {
201 
202         return (this.authType);
203 
204     }
205 
206 
207     /**
208      * Set the authentication type used to authenticate our cached
209      * Principal, if any.
210      *
211      * @param authType The new cached authentication type
212      */
213     public void setAuthType(String authType) {
214 
215         String oldAuthType = this.authType;
216         this.authType = authType;
217         support.firePropertyChange("authType", oldAuthType, this.authType);
218 
219     }
220 
221 
222     /**
223      * Set the creation time for this session.  This method is called by the
224      * Manager when an existing Session instance is reused.
225      *
226      * @param time The new creation time
227      */
228     public void setCreationTime(long time) {
229 
230         this.creationTime = time;
231         this.lastAccessedTime = time;
232         this.thisAccessedTime = time;
233 
234     }
235 
236 
237     /**
238      * Return the session identifier for this session.
239      */
240     public String getId() {
241 
242         return (this.id);
243 
244     }
245 
246 
247     /**
248      * Set the session identifier for this session.
249      *
250      * @param id The new session identifier
251      */
252     public void setId(String id) {
253 
254         this.id = id;
255 
256         // Notify interested session event listeners
257         ///fireSessionEvent(Session.SESSION_CREATED_EVENT, null);
258 
259     }
260 
261 
262     /**
263      * Return the last time the client sent a request associated with this
264      * session, as the number of milliseconds since midnight, January 1, 1970
265      * GMT.  Actions that your application takes, such as getting or setting
266      * a value associated with the session, do not affect the access time.
267      */
268     public long getLastAccessedTime() {
269 
270         return (this.lastAccessedTime);
271 
272     }
273 
274 
275     /**
276      * Return the maximum time interval, in seconds, between client requests
277      * before the servlet container will invalidate the session.  A negative
278      * time indicates that the session should never time out.
279      *
280      * @exception IllegalStateException if this method is called on
281      *  an invalidated session
282      */
283     public int getMaxInactiveInterval() {
284 
285         if (!isValid) {
286             throw new IllegalStateException( "Cannot call getMaxInactiveInterval on an invalidated session" );
287         }
288 
289         return (this.maxInactiveInterval);
290 
291     }
292 
293 
294     /**
295      * Set the maximum time interval, in seconds, between client requests
296      * before the servlet container will invalidate the session.  A negative
297      * time indicates that the session should never time out.
298      *
299      * @param interval The new maximum interval
300      */
301     public void setMaxInactiveInterval(int interval) {
302 
303         this.maxInactiveInterval = interval;
304 
305     }
306 
307 
308     /**
309      * Set the <code>isNew</code> flag for this session.
310      *
311      * @param isNew The new value for the <code>isNew</code> flag
312      */
313     public void setNew(boolean isNew) {
314 
315         this.isNew = isNew;
316 
317     }
318 
319 
320     /**
321      * Return the authenticated Principal that is associated with this Session.
322      * This provides an <code>Authenticator</code> with a means to cache a
323      * previously authenticated Principal, and avoid potentially expensive
324      * <code>Realm.authenticate()</code> calls on every request.  If there
325      * is no current associated Principal, return <code>null</code>.
326      */
327     public Principal getPrincipal() {
328 
329         return (this.principal);
330 
331     }
332 
333 
334     /**
335      * Set the authenticated Principal that is associated with this Session.
336      * This provides an <code>Authenticator</code> with a means to cache a
337      * previously authenticated Principal, and avoid potentially expensive
338      * <code>Realm.authenticate()</code> calls on every request.
339      *
340      * @param principal The new Principal, or <code>null</code> if none
341      */
342     public void setPrincipal(Principal principal) {
343 
344         Principal oldPrincipal = this.principal;
345         this.principal = principal;
346         support.firePropertyChange("principal", oldPrincipal, this.principal);
347 
348     }
349 
350 
351 
352     /**
353      * Return the <code>isValid</code> flag for this session.
354      */
355     public boolean isValid() {
356 
357         return (this.isValid);
358 
359     }
360 
361 
362     /**
363      * Set the <code>isValid</code> flag for this session.
364      *
365      * @param isValid The new value for the <code>isValid</code> flag
366      */
367     public void setValid(boolean isValid) {
368 
369         this.isValid = isValid;
370     }
371 
372 
373     // ------------------------------------------------- Session Public Methods
374 
375 
376     /**
377      * Update the accessed time information for this session.  This method
378      * should be called by the context when a request comes in for a particular
379      * session, even if the application does not reference it.
380      */
381     public void access() {
382 
383         this.isNew = false;
384         this.lastAccessedTime = this.thisAccessedTime;
385         this.thisAccessedTime = System.currentTimeMillis();
386 
387     }
388 
389 
390 
391     /**
392      * Perform the internal processing required to invalidate this session,
393      * without triggering an exception if the session has already expired.
394      */
395     public void expire() {
396 
397         // Mark this session as "being expired" if needed
398         if (expiring)
399             return;
400         expiring = true;
401         setValid(false);
402 
403         // Unbind any objects associated with this session
404         String keys[] = keys();
405         for (int i = 0; i < keys.length; i++)
406             removeAttribute(keys[i]);
407 
408         // Notify interested session event listeners
409         //fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
410 
411         // We have completed expire of this session
412         expiring = false;
413     }
414 
415 
416     /**
417      * Perform the internal processing required to passivate
418      * this session.
419      */
420     public void passivate() {
421 
422         // Notify ActivationListeners
423         HttpSessionEvent event = null;
424         String keys[] = keys();
425         for (int i = 0; i < keys.length; i++) {
426             Object attribute = getAttribute(keys[i]);
427             if (attribute instanceof HttpSessionActivationListener) {
428                 if (event == null)
429                     event = new HttpSessionEvent(this);
430                 // FIXME: Should we catch throwables?
431                 ((HttpSessionActivationListener)attribute).sessionWillPassivate(event);
432             }
433         }
434 
435     }
436 
437 
438     /**
439      * Perform internal processing required to activate this
440      * session.
441      */
442     public void activate() {
443 
444         // Notify ActivationListeners
445         HttpSessionEvent event = null;
446         String keys[] = keys();
447         for (int i = 0; i < keys.length; i++) {
448             Object attribute = getAttribute(keys[i]);
449             if (attribute instanceof HttpSessionActivationListener) {
450                 if (event == null)
451                     event = new HttpSessionEvent(this);
452                 // FIXME: Should we catch throwables?
453                 ((HttpSessionActivationListener)attribute).sessionDidActivate(event);
454             }
455         }
456 
457     }
458 
459 
460     /**
461      * Return the object bound with the specified name to the internal notes
462      * for this session, or <code>null</code> if no such binding exists.
463      *
464      * @param name Name of the note to be returned
465      */
466     public Object getNote(String name) {
467 
468         synchronized (notes) {
469             return (notes.get(name));
470         }
471 
472     }
473 
474 
475     /**
476      * Return an Iterator containing the String names of all notes bindings
477      * that exist for this session.
478      */
479     public Iterator getNoteNames() {
480 
481         synchronized (notes) {
482             return (notes.keySet().iterator());
483         }
484 
485     }
486 
487 
488     /**
489      * Release all object references, and initialize instance variables, in
490      * preparation for reuse of this object.
491      */
492     public void recycle() {
493 
494         // Reset the instance variables associated with this Session
495         attributes.clear();
496         setAuthType(null);
497         creationTime = 0L;
498         expiring = false;
499         id = null;
500         lastAccessedTime = 0L;
501         maxInactiveInterval = -1;
502         setPrincipal(null);
503         isNew = false;
504         isValid = false;
505     }
506 
507 
508     /**
509      * Remove any object bound to the specified name in the internal notes
510      * for this session.
511      *
512      * @param name Name of the note to be removed
513      */
514     public void removeNote(String name) {
515 
516         synchronized (notes) {
517             notes.remove(name);
518         }
519 
520     }
521 
522 
523 
524     /**
525      * Bind an object to a specified name in the internal notes associated
526      * with this session, replacing any existing binding for this name.
527      *
528      * @param name Name to which the object should be bound
529      * @param value Object to be bound to the specified name
530      */
531     public void setNote(String name, Object value) {
532 
533         synchronized (notes) {
534             notes.put(name, value);
535         }
536 
537     }
538 
539 
540     /**
541      * Return a string representation of this object.
542      */
543     public String toString() {
544 
545         StringBuffer sb = new StringBuffer();
546         sb.append("StandardSession[");
547         sb.append(id);
548         sb.append("]");
549         return (sb.toString());
550 
551     }
552 
553 
554     // ------------------------------------------------ Session Package Methods
555 
556 
557     /**
558      * Read a serialized version of the contents of this session object from
559      * the specified object input stream, without requiring that the
560      * StandardSession itself have been serialized.
561      *
562      * @param stream The object input stream to read from
563      *
564      * @exception ClassNotFoundException if an unknown class is specified
565      * @exception IOException if an input/output error occurs
566      */
567     void readObjectData(ObjectInputStream stream)
568         throws ClassNotFoundException, IOException {
569 
570         readObject(stream);
571 
572     }
573 
574 
575     /**
576      * Write a serialized version of the contents of this session object to
577      * the specified object output stream, without requiring that the
578      * StandardSession itself have been serialized.
579      *
580      * @param stream The object output stream to write to
581      *
582      * @exception IOException if an input/output error occurs
583      */
584     void writeObjectData(ObjectOutputStream stream)
585         throws IOException {
586 
587         writeObject(stream);
588 
589     }
590 
591 
592     // ------------------------------------------------- HttpSession Properties
593 
594 
595     /**
596      * Return the time when this session was created, in milliseconds since
597      * midnight, January 1, 1970 GMT.
598      *
599      * @exception IllegalStateException if this method is called on an
600      *  invalidated session
601      */
602     public long getCreationTime() {
603 
604         if (!isValid) {
605             throw new IllegalStateException( "Cannot call getCreationTime() on invalidated session" );
606         }
607 
608         return (this.creationTime);
609 
610     }
611 
612 
613     /**
614      * Return the ServletContext to which this session belongs.
615      */
616     public ServletContext getServletContext() {
617         return servletContext;
618 
619     }
620 
621 
622     /**
623      * Return the session context with which this session is associated.
624      *
625      * @deprecated As of Version 2.1, this method is deprecated and has no
626      *  replacement.  It will be removed in a future version of the
627      *  Java Servlet API.
628      */
629     public HttpSessionContext getSessionContext() {
630 
631         return null;
632 
633     }
634 
635 
636     // ----------------------------------------------HttpSession Public Methods
637 
638 
639     /**
640      * Return the object bound with the specified name in this session, or
641      * <code>null</code> if no object is bound with that name.
642      *
643      * @param name Name of the attribute to be returned
644      *
645      * @exception IllegalStateException if this method is called on an
646      *  invalidated session
647      */
648     public Object getAttribute(String name) {
649 
650         if (!isValid) {
651             throw new IllegalStateException( "Cannot call getAttribute() on invalidated session" );
652         }
653 
654         synchronized (attributes) {
655             return (attributes.get(name));
656         }
657 
658     }
659 
660 
661     /**
662      * Return an <code>Enumeration</code> of <code>String</code> objects
663      * containing the names of the objects bound to this session.
664      *
665      * @exception IllegalStateException if this method is called on an
666      *  invalidated session
667      */
668     public Enumeration getAttributeNames() {
669 
670         if (!isValid) {
671             throw new IllegalStateException( "Cannot call getAttributeNames() on invalidated session" );
672         }
673 
674         synchronized (attributes) {
675             return (new IteratorEnumeration(attributes.keySet().iterator()));
676         }
677 
678     }
679 
680 
681     /**
682      * Return the object bound with the specified name in this session, or
683      * <code>null</code> if no object is bound with that name.
684      *
685      * @param name Name of the value to be returned
686      *
687      * @exception IllegalStateException if this method is called on an
688      *  invalidated session
689      *
690      * @deprecated As of Version 2.2, this method is replaced by
691      *  <code>getAttribute()</code>
692      */
693     public Object getValue(String name) {
694 
695         return (getAttribute(name));
696 
697     }
698 
699 
700     /**
701      * Return the set of names of objects bound to this session.  If there
702      * are no such objects, a zero-length array is returned.
703      *
704      * @exception IllegalStateException if this method is called on an
705      *  invalidated session
706      *
707      * @deprecated As of Version 2.2, this method is replaced by
708      *  <code>getAttributeNames()</code>
709      */
710     public String[] getValueNames() {
711 
712         if (!isValid) {
713             throw new IllegalStateException( "Cannot call getValueNames() on invalidated session" );
714         }
715 
716         return (keys());
717 
718     }
719 
720 
721     /**
722      * Invalidates this session and unbinds any objects bound to it.
723      *
724      * @exception IllegalStateException if this method is called on
725      *  an invalidated session
726      */
727     public void invalidate() {
728 
729         if (!isValid) {
730             throw new IllegalStateException( "Cannot call invalidate() on invalidated session" );
731         }
732 
733         // Cause this session to expire
734         expire();
735 
736     }
737 
738 
739     /**
740      * Return <code>true</code> if the client does not yet know about the
741      * session, or if the client chooses not to join the session.  For
742      * example, if the server used only cookie-based sessions, and the client
743      * has disabled the use of cookies, then a session would be new on each
744      * request.
745      *
746      * @exception IllegalStateException if this method is called on an
747      *  invalidated session
748      */
749     public boolean isNew() {
750 
751         if (!isValid) {
752             throw new IllegalStateException( "Cannot call isNew() on invalidated session" );
753         }
754 
755         return (this.isNew);
756 
757     }
758 
759 
760     /**
761      * Bind an object to this session, using the specified name.  If an object
762      * of the same name is already bound to this session, the object is
763      * replaced.
764      * <p>
765      * After this method executes, and if the object implements
766      * <code>HttpSessionBindingListener</code>, the container calls
767      * <code>valueBound()</code> on the object.
768      *
769      * @param name Name to which the object is bound, cannot be null
770      * @param value Object to be bound, cannot be null
771      *
772      * @exception IllegalStateException if this method is called on an
773      *  invalidated session
774      *
775      * @deprecated As of Version 2.2, this method is replaced by
776      *  <code>setAttribute()</code>
777      */
778     public void putValue(String name, Object value) {
779 
780         setAttribute(name, value);
781 
782     }
783 
784 
785     /**
786      * Remove the object bound with the specified name from this session.  If
787      * the session does not have an object bound with this name, this method
788      * does nothing.
789      * <p>
790      * After this method executes, and if the object implements
791      * <code>HttpSessionBindingListener</code>, the container calls
792      * <code>valueUnbound()</code> on the object.
793      *
794      * @param name Name of the object to remove from this session.
795      *
796      * @exception IllegalStateException if this method is called on an
797      *  invalidated session
798      */
799     public void removeAttribute(String name) {
800 
801         // Validate our current state
802         if (!expiring && !isValid) { 
803             throw new IllegalStateException( "Cannot call removeAttribute() on an invalidated session" );
804         }
805 
806         // Remove this attribute from our collection
807         Object value = null;
808         boolean found = false;
809         synchronized (attributes) {
810             found = attributes.containsKey(name);
811             if (found) {
812                 value = attributes.get(name);
813                 attributes.remove(name);
814             } else {
815                 return;
816             }
817         }
818 /*
819         // Call the valueUnbound() method if necessary
820         HttpSessionBindingEvent event =
821           new HttpSessionBindingEvent((HttpSession) this, name, value);
822         if ((value != null) &&
823             (value instanceof HttpSessionBindingListener))
824             ((HttpSessionBindingListener) value).valueUnbound(event);
825 
826         // Notify interested application event listeners
827         StandardContext context = (StandardContext) manager.getContainer();
828         Object listeners[] = context.getApplicationListeners();
829         if (listeners == null)
830             return;
831         for (int i = 0; i < listeners.length; i++) {
832             if (!(listeners[i] instanceof HttpSessionAttributeListener))
833                 continue;
834             HttpSessionAttributeListener listener =
835                 (HttpSessionAttributeListener) listeners[i];
836             try {
837                 context.fireContainerEvent("beforeSessionAttributeRemoved",
838                                            listener);
839                 listener.attributeRemoved(event);
840                 context.fireContainerEvent("afterSessionAttributeRemoved",
841                                            listener);
842             } catch (Throwable t) {
843                 context.fireContainerEvent("afterSessionAttributeRemoved",
844                                            listener);
845                 // FIXME - should we do anything besides log these?
846                 log( "Exception firing attribute event", t);
847             }
848         }
849 */
850     }
851 
852 
853     /**
854      * Remove the object bound with the specified name from this session.  If
855      * the session does not have an object bound with this name, this method
856      * does nothing.
857      * <p>
858      * After this method executes, and if the object implements
859      * <code>HttpSessionBindingListener</code>, the container calls
860      * <code>valueUnbound()</code> on the object.
861      *
862      * @param name Name of the object to remove from this session.
863      *
864      * @exception IllegalStateException if this method is called on an
865      *  invalidated session
866      *
867      * @deprecated As of Version 2.2, this method is replaced by
868      *  <code>removeAttribute()</code>
869      */
870     public void removeValue(String name) {
871 
872         removeAttribute(name);
873 
874     }
875 
876 
877     /**
878      * Bind an object to this session, using the specified name.  If an object
879      * of the same name is already bound to this session, the object is
880      * replaced.
881      * <p>
882      * After this method executes, and if the object implements
883      * <code>HttpSessionBindingListener</code>, the container calls
884      * <code>valueBound()</code> on the object.
885      *
886      * @param name Name to which the object is bound, cannot be null
887      * @param value Object to be bound, cannot be null
888      *
889      * @exception IllegalArgumentException if an attempt is made to add a
890      *  non-serializable object in an environment marked distributable.
891      * @exception IllegalStateException if this method is called on an
892      *  invalidated session
893      */
894     public void setAttribute(String name, Object value) {
895 
896         // Name cannot be null
897         if (name == null) {
898             throw new IllegalArgumentException( "Attribute name cannot be null" );
899         }
900 
901         // Null value is the same as removeAttribute()
902         if (value == null) {
903             removeAttribute(name);
904             return;
905         }
906 
907         // Validate our current state
908         if (!isValid) {
909             throw new IllegalStateException( "You cannot call this method on an invalidated session" );
910         }
911         if (distributable &&
912             !(value instanceof Serializable)) {
913             throw new IllegalArgumentException( "Attribute value must be Serializable" );
914         }
915 
916         // Replace or add this attribute
917         Object unbound = null;
918         synchronized (attributes) {
919             unbound = attributes.get(name);
920             attributes.put(name, value);
921         }
922 
923         // Call the valueUnbound() method if necessary
924         if ((unbound != null) &&
925             (unbound instanceof HttpSessionBindingListener)) {
926             ((HttpSessionBindingListener) unbound).valueUnbound
927               (new HttpSessionBindingEvent((HttpSession) this, name));
928         }
929 
930         // Call the valueBound() method if necessary
931         HttpSessionBindingEvent event = null;
932         if (unbound != null)
933             event = new HttpSessionBindingEvent
934                 ((HttpSession) this, name, unbound);
935         else
936             event = new HttpSessionBindingEvent
937                 ((HttpSession) this, name, value);
938         if (value instanceof HttpSessionBindingListener)
939             ((HttpSessionBindingListener) value).valueBound(event);
940 
941         // Notify interested application event listeners
942 /*        
943         StandardContext context = (StandardContext) manager.getContainer();
944         Object listeners[] = context.getApplicationListeners();
945         if (listeners == null)
946             return;
947         for (int i = 0; i < listeners.length; i++) {
948             if (!(listeners[i] instanceof HttpSessionAttributeListener))
949                 continue;
950             HttpSessionAttributeListener listener =
951                 (HttpSessionAttributeListener) listeners[i];
952             try {
953                 if (unbound != null) {
954                     context.fireContainerEvent("beforeSessionAttributeReplaced",
955                                                listener);
956                     listener.attributeReplaced(event);
957                     context.fireContainerEvent("afterSessionAttributeReplaced",
958                                                listener);
959                 } else {
960                     context.fireContainerEvent("beforeSessionAttributeAdded",
961                                                listener);
962                     listener.attributeAdded(event);
963                     context.fireContainerEvent("afterSessionAttributeAdded",
964                                                listener);
965                 }
966             } catch (Throwable t) {
967                 if (unbound != null)
968                     context.fireContainerEvent("afterSessionAttributeReplaced",
969                                                listener);
970                 else
971                     context.fireContainerEvent("afterSessionAttributeAdded",
972                                                listener);
973                 // FIXME - should we do anything besides log these?
974                 log( "Exception firing attribute event", t);
975             }
976         }
977 */
978     }
979 
980 
981     // -------------------------------------------- HttpSession Private Methods
982 
983 
984     /**
985      * Read a serialized version of this session object from the specified
986      * object input stream.
987      * <p>
988      * <b>IMPLEMENTATION NOTE</b>:  The reference to the owning Manager
989      * is not restored by this method, and must be set explicitly.
990      *
991      * @param stream The input stream to read from
992      *
993      * @exception ClassNotFoundException if an unknown class is specified
994      * @exception IOException if an input/output error occurs
995      */
996     private void readObject(ObjectInputStream stream)
997         throws ClassNotFoundException, IOException {
998 
999         // Deserialize the scalar instance variables (except Manager)
1000         authType = null;        // Transient only
1001         creationTime = ((Long) stream.readObject()).longValue();
1002         lastAccessedTime = ((Long) stream.readObject()).longValue();
1003         maxInactiveInterval = ((Integer) stream.readObject()).intValue();
1004         isNew = ((Boolean) stream.readObject()).booleanValue();
1005         isValid = ((Boolean) stream.readObject()).booleanValue();
1006         thisAccessedTime = ((Long) stream.readObject()).longValue();
1007         principal = null;        // Transient only
1008         setId((String) stream.readObject());
1009         if (debug >= 2)
1010             log("readObject() loading session " + id);
1011 
1012         // Deserialize the attribute count and attribute values
1013         if (attributes == null)
1014             attributes = new HashMap();
1015         int n = ((Integer) stream.readObject()).intValue();
1016         boolean isValidSave = isValid;
1017         isValid = true;
1018         for (int i = 0; i < n; i++) {
1019             String name = (String) stream.readObject();
1020             Object value = (Object) stream.readObject();
1021             if ((value instanceof String) && (value.equals(NOT_SERIALIZED)))
1022                 continue;
1023             if (debug >= 2)
1024                 log("  loading attribute '" + name +
1025                     "' with value '" + value + "'");
1026             synchronized (attributes) {
1027                 attributes.put(name, value);
1028             }
1029         }
1030         isValid = isValidSave;
1031 
1032     }
1033 
1034 
1035     /**
1036      * Write a serialized version of this session object to the specified
1037      * object output stream.
1038      * <p>
1039      * <b>IMPLEMENTATION NOTE</b>:  The owning Manager will not be stored
1040      * in the serialized representation of this Session.  After calling
1041      * <code>readObject()</code>, you must set the associated Manager
1042      * explicitly.
1043      * <p>
1044      * <b>IMPLEMENTATION NOTE</b>:  Any attribute that is not Serializable
1045      * will be unbound from the session, with appropriate actions if it
1046      * implements HttpSessionBindingListener.  If you do not want any such
1047      * attributes, be sure the <code>distributable</code> property of the
1048      * associated Manager is set to <code>true</code>.
1049      *
1050      * @param stream The output stream to write to
1051      *
1052      * @exception IOException if an input/output error occurs
1053      */
1054     private void writeObject(ObjectOutputStream stream) throws IOException {
1055 
1056         // Write the scalar instance variables (except Manager)
1057         stream.writeObject(new Long(creationTime));
1058         stream.writeObject(new Long(lastAccessedTime));
1059         stream.writeObject(new Integer(maxInactiveInterval));
1060         stream.writeObject(new Boolean(isNew));
1061         stream.writeObject(new Boolean(isValid));
1062         stream.writeObject(new Long(thisAccessedTime));
1063         stream.writeObject(id);
1064         if (debug >= 2)
1065             log("writeObject() storing session " + id);
1066 
1067         // Accumulate the names of serializable and non-serializable attributes
1068         String keys[] = keys();
1069         ArrayList saveNames = new ArrayList();
1070         ArrayList saveValues = new ArrayList();
1071         ArrayList unbinds = new ArrayList();
1072         for (int i = 0; i < keys.length; i++) {
1073             Object value = null;
1074             synchronized (attributes) {
1075                 value = attributes.get(keys[i]);
1076             }
1077             if (value == null)
1078                 continue;
1079             else if (value instanceof Serializable) {
1080                 saveNames.add(keys[i]);
1081                 saveValues.add(value);
1082             } else
1083                 unbinds.add(keys[i]);
1084         }
1085 
1086         // Serialize the attribute count and the Serializable attributes
1087         int n = saveNames.size();
1088         stream.writeObject(new Integer(n));
1089         for (int i = 0; i < n; i++) {
1090             stream.writeObject((String) saveNames.get(i));
1091             try {
1092                 stream.writeObject(saveValues.get(i));
1093                 if (debug >= 2)
1094                     log("  storing attribute '" + saveNames.get(i) +
1095                         "' with value '" + saveValues.get(i) + "'");
1096             } catch (NotSerializableException e) {
1097                 log( "Session is not serializable for attribute name: " + saveNames.get(i) + " and session id: " + id, e);
1098                 stream.writeObject(NOT_SERIALIZED);
1099                 if (debug >= 2)
1100                     log("  storing attribute '" + saveNames.get(i) +
1101                         "' with value NOT_SERIALIZED");
1102                 unbinds.add(saveNames.get(i));
1103             }
1104         }
1105 
1106         // Unbind the non-Serializable attributes
1107         Iterator names = unbinds.iterator();
1108         while (names.hasNext()) {
1109             removeAttribute((String) names.next());
1110         }
1111 
1112     }
1113 
1114 
1115     // -------------------------------------------------------- Private Methods
1116 
1117 
1118     /**
1119      * Notify all session event listeners that a particular event has
1120      * occurred for this Session.  The default implementation performs
1121      * this notification synchronously using the calling thread.
1122      *
1123      * @param type Event type
1124      * @param data Event data
1125      */
1126     public void fireSessionEvent(String type, Object data) {
1127 /*    
1128         if (listeners.size() < 1)
1129             return;
1130         SessionEvent event = new SessionEvent(this, type, data);
1131         SessionListener list[] = new SessionListener[0];
1132         synchronized (listeners) {
1133             list = (SessionListener[]) listeners.toArray(list);
1134         }
1135         for (int i = 0; i < list.length; i++)
1136             ((SessionListener) list[i]).sessionEvent(event);
1137 */
1138     }
1139 
1140 
1141     /**
1142      * Return the names of all currently defined session attributes
1143      * as an array of Strings.  If there are no defined attributes, a
1144      * zero-length array is returned.
1145      */
1146     private String[] keys() {
1147 
1148         String results[] = new String[0];
1149         synchronized (attributes) {
1150             return ((String[]) attributes.keySet().toArray(results));
1151         }
1152 
1153     }
1154 
1155 
1156     /**
1157      * Log a message to the current ServletContext
1158      *
1159      * @param message Message to be logged
1160      */
1161     protected void log(String message) {
1162 
1163         servletContext.log(message);
1164 
1165     }
1166 
1167 
1168     /**
1169      * Log a message to the current ServletContext
1170      *
1171      * @param message Message to be logged
1172      * @param throwable Associated exception
1173      */
1174     protected void log(String message, Throwable throwable) {
1175 
1176         servletContext.log(message, throwable);
1177 
1178     }
1179 
1180 
1181 }