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 }