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 }