001    /*
002     * Copyright 1999,2004 The Apache Software Foundation.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    
018    package org.apache.commons.messagelet.impl;
019    
020    
021    import java.beans.PropertyChangeSupport;
022    import java.io.IOException;
023    import java.io.NotSerializableException;
024    import java.io.ObjectInputStream;
025    import java.io.ObjectOutputStream;
026    import java.io.Serializable;
027    import java.security.Principal;
028    import java.util.ArrayList;
029    import java.util.Enumeration;
030    import java.util.HashMap;
031    import java.util.Iterator;
032    
033    import javax.servlet.ServletContext;
034    import javax.servlet.http.HttpSession;
035    import javax.servlet.http.HttpSessionActivationListener;
036    import javax.servlet.http.HttpSessionBindingEvent;
037    import javax.servlet.http.HttpSessionBindingListener;
038    import javax.servlet.http.HttpSessionContext;
039    import javax.servlet.http.HttpSessionEvent;
040    
041    import org.apache.commons.collections.iterators.IteratorEnumeration;
042    
043    /**
044     * Based on the Catalina StandardSession class.
045     * Standard implementation of the <b>HttpSession</b> interface.  This object is
046     * serializable, so that it can be stored in persistent storage or transferred
047     * to a different JVM for distributable session support.
048     * <p>
049     *
050     * @author Craig R. McClanahan
051     * @author Sean Legassick
052     * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
053     * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
054     * @version $Revision: 155459 $ $Date: 2005-02-26 13:24:44 +0000 (Sat, 26 Feb 2005) $
055     */
056    
057    public class HttpSessionImpl implements HttpSession, Serializable {
058    
059    
060    
061        // ----------------------------------------------------- Instance Variables
062    
063    
064        /**
065         * The dummy attribute value serialized when a NotSerializableException is
066         * encountered in <code>writeObject()</code>.
067         */
068        private static final String NOT_SERIALIZED =
069            "___NOT_SERIALIZABLE_EXCEPTION___";
070    
071    
072        /**
073         * The collection of user data attributes associated with this Session.
074         */
075        private HashMap attributes = new HashMap();
076    
077    
078        /**
079         * The authentication type used to authenticate our cached Principal,
080         * if any.  NOTE:  This value is not included in the serialized
081         * version of this object.
082         */
083        private transient String authType = null;
084    
085    
086        /**
087         * The time this session was created, in milliseconds since midnight,
088         * January 1, 1970 GMT.
089         */
090        private long creationTime = 0L;
091    
092    
093        /**
094         * The debugging detail level for this component.  NOTE:  This value
095         * is not included in the serialized version of this object.
096         */
097        private transient int debug = 0;
098    
099    
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    }