1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.session;
20
21
22 import java.beans.PropertyChangeSupport;
23 import java.io.IOException;
24 import java.io.NotSerializableException;
25 import java.io.ObjectInputStream;
26 import java.io.ObjectOutputStream;
27 import java.io.Serializable;
28 import java.lang.reflect.Method;
29 import java.security.AccessController;
30 import java.security.Principal;
31 import java.security.PrivilegedAction;
32 import java.util.ArrayList;
33 import java.util.Enumeration;
34 import java.util.HashMap;
35 import java.util.Hashtable;
36 import java.util.Iterator;
37 import java.util.Map;
38 import java.util.concurrent.ConcurrentHashMap;
39 import java.util.concurrent.atomic.AtomicInteger;
40
41 import javax.servlet.ServletContext;
42 import javax.servlet.http.HttpSession;
43 import javax.servlet.http.HttpSessionActivationListener;
44 import javax.servlet.http.HttpSessionAttributeListener;
45 import javax.servlet.http.HttpSessionBindingEvent;
46 import javax.servlet.http.HttpSessionBindingListener;
47 import javax.servlet.http.HttpSessionContext;
48 import javax.servlet.http.HttpSessionEvent;
49 import javax.servlet.http.HttpSessionListener;
50
51 import org.apache.catalina.Context;
52 import org.apache.catalina.Globals;
53 import org.apache.catalina.Manager;
54 import org.apache.catalina.Session;
55 import org.apache.catalina.SessionEvent;
56 import org.apache.catalina.SessionListener;
57 import org.apache.catalina.util.Enumerator;
58 import org.apache.catalina.util.StringManager;
59
60 import org.apache.catalina.security.SecurityUtil;
61
62 /**
63 * Standard implementation of the <b>Session</b> interface. This object is
64 * serializable, so that it can be stored in persistent storage or transferred
65 * to a different JVM for distributable session support.
66 * <p>
67 * <b>IMPLEMENTATION NOTE</b>: An instance of this class represents both the
68 * internal (Session) and application level (HttpSession) view of the session.
69 * However, because the class itself is not declared public, Java logic outside
70 * of the <code>org.apache.catalina.session</code> package cannot cast an
71 * HttpSession view of this instance back to a Session view.
72 * <p>
73 * <b>IMPLEMENTATION NOTE</b>: If you add fields to this class, you must
74 * make sure that you carry them over in the read/writeObject methods so
75 * that this class is properly serialized.
76 *
77 * @author Craig R. McClanahan
78 * @author Sean Legassick
79 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
80 * @version $Revision: 505593 $ $Date: 2007-02-10 01:54:56 +0100 (sam., 10 févr. 2007) $
81 */
82
83 public class StandardSession
84 implements HttpSession, Session, Serializable {
85
86
87 protected static final boolean ACTIVITY_CHECK =
88 Globals.STRICT_SERVLET_COMPLIANCE
89 || Boolean.valueOf(System.getProperty("org.apache.catalina.session.StandardSession.ACTIVITY_CHECK", "false")).booleanValue();
90
91
92 // ----------------------------------------------------------- Constructors
93
94
95 /**
96 * Construct a new Session associated with the specified Manager.
97 *
98 * @param manager The manager with which this Session is associated
99 */
100 public StandardSession(Manager manager) {
101
102 super();
103 this.manager = manager;
104
105 // Initialize access count
106 if (ACTIVITY_CHECK) {
107 accessCount = new AtomicInteger();
108 }
109
110 }
111
112
113 // ----------------------------------------------------- Instance Variables
114
115
116 /**
117 * Type array.
118 */
119 protected static final String EMPTY_ARRAY[] = new String[0];
120
121
122 /**
123 * The dummy attribute value serialized when a NotSerializableException is
124 * encountered in <code>writeObject()</code>.
125 */
126 protected static final String NOT_SERIALIZED =
127 "___NOT_SERIALIZABLE_EXCEPTION___";
128
129
130 /**
131 * The collection of user data attributes associated with this Session.
132 */
133 protected Map attributes = new ConcurrentHashMap();
134
135
136 /**
137 * The authentication type used to authenticate our cached Principal,
138 * if any. NOTE: This value is not included in the serialized
139 * version of this object.
140 */
141 protected transient String authType = null;
142
143
144 /**
145 * The <code>java.lang.Method</code> for the
146 * <code>fireContainerEvent()</code> method of the
147 * <code>org.apache.catalina.core.StandardContext</code> method,
148 * if our Context implementation is of this class. This value is
149 * computed dynamically the first time it is needed, or after
150 * a session reload (since it is declared transient).
151 */
152 protected transient Method containerEventMethod = null;
153
154
155 /**
156 * The method signature for the <code>fireContainerEvent</code> method.
157 */
158 protected static final Class containerEventTypes[] =
159 { String.class, Object.class };
160
161
162 /**
163 * The time this session was created, in milliseconds since midnight,
164 * January 1, 1970 GMT.
165 */
166 protected long creationTime = 0L;
167
168
169 /**
170 * Set of attribute names which are not allowed to be persisted.
171 */
172 protected static final String[] excludedAttributes = {
173 Globals.SUBJECT_ATTR
174 };
175
176
177 /**
178 * We are currently processing a session expiration, so bypass
179 * certain IllegalStateException tests. NOTE: This value is not
180 * included in the serialized version of this object.
181 */
182 protected transient boolean expiring = false;
183
184
185 /**
186 * The facade associated with this session. NOTE: This value is not
187 * included in the serialized version of this object.
188 */
189 protected transient StandardSessionFacade facade = null;
190
191
192 /**
193 * The session identifier of this Session.
194 */
195 protected String id = null;
196
197
198 /**
199 * Descriptive information describing this Session implementation.
200 */
201 protected static final String info = "StandardSession/1.0";
202
203
204 /**
205 * The last accessed time for this Session.
206 */
207 protected long lastAccessedTime = creationTime;
208
209
210 /**
211 * The session event listeners for this Session.
212 */
213 protected transient ArrayList listeners = new ArrayList();
214
215
216 /**
217 * The Manager with which this Session is associated.
218 */
219 protected transient Manager manager = null;
220
221
222 /**
223 * The maximum time interval, in seconds, between client requests before
224 * the servlet container may invalidate this session. A negative time
225 * indicates that the session should never time out.
226 */
227 protected int maxInactiveInterval = -1;
228
229
230 /**
231 * Flag indicating whether this session is new or not.
232 */
233 protected boolean isNew = false;
234
235
236 /**
237 * Flag indicating whether this session is valid or not.
238 */
239 protected boolean isValid = false;
240
241
242 /**
243 * Internal notes associated with this session by Catalina components
244 * and event listeners. <b>IMPLEMENTATION NOTE:</b> This object is
245 * <em>not</em> saved and restored across session serializations!
246 */
247 protected transient Map notes = new Hashtable();
248
249
250 /**
251 * The authenticated Principal associated with this session, if any.
252 * <b>IMPLEMENTATION NOTE:</b> This object is <i>not</i> saved and
253 * restored across session serializations!
254 */
255 protected transient Principal principal = null;
256
257
258 /**
259 * The string manager for this package.
260 */
261 protected static StringManager sm =
262 StringManager.getManager(Constants.Package);
263
264
265 /**
266 * The HTTP session context associated with this session.
267 */
268 protected static HttpSessionContext sessionContext = null;
269
270
271 /**
272 * The property change support for this component. NOTE: This value
273 * is not included in the serialized version of this object.
274 */
275 protected transient PropertyChangeSupport support =
276 new PropertyChangeSupport(this);
277
278
279 /**
280 * The current accessed time for this session.
281 */
282 protected long thisAccessedTime = creationTime;
283
284
285 /**
286 * The access count for this session.
287 */
288 protected transient AtomicInteger accessCount = null;
289
290
291 // ----------------------------------------------------- Session Properties
292
293
294 /**
295 * Return the authentication type used to authenticate our cached
296 * Principal, if any.
297 */
298 public String getAuthType() {
299
300 return (this.authType);
301
302 }
303
304
305 /**
306 * Set the authentication type used to authenticate our cached
307 * Principal, if any.
308 *
309 * @param authType The new cached authentication type
310 */
311 public void setAuthType(String authType) {
312
313 String oldAuthType = this.authType;
314 this.authType = authType;
315 support.firePropertyChange("authType", oldAuthType, this.authType);
316
317 }
318
319
320 /**
321 * Set the creation time for this session. This method is called by the
322 * Manager when an existing Session instance is reused.
323 *
324 * @param time The new creation time
325 */
326 public void setCreationTime(long time) {
327
328 this.creationTime = time;
329 this.lastAccessedTime = time;
330 this.thisAccessedTime = time;
331
332 }
333
334
335 /**
336 * Return the session identifier for this session.
337 */
338 public String getId() {
339
340 return (this.id);
341
342 }
343
344
345 /**
346 * Return the session identifier for this session.
347 */
348 public String getIdInternal() {
349
350 return (this.id);
351
352 }
353
354
355 /**
356 * Set the session identifier for this session.
357 *
358 * @param id The new session identifier
359 */
360 public void setId(String id) {
361
362 if ((this.id != null) && (manager != null))
363 manager.remove(this);
364
365 this.id = id;
366
367 if (manager != null)
368 manager.add(this);
369 tellNew();
370 }
371
372
373 /**
374 * Inform the listeners about the new session.
375 *
376 */
377 public void tellNew() {
378
379 // Notify interested session event listeners
380 fireSessionEvent(Session.SESSION_CREATED_EVENT, null);
381
382 // Notify interested application event listeners
383 Context context = (Context) manager.getContainer();
384 Object listeners[] = context.getApplicationLifecycleListeners();
385 if (listeners != null) {
386 HttpSessionEvent event =
387 new HttpSessionEvent(getSession());
388 for (int i = 0; i < listeners.length; i++) {
389 if (!(listeners[i] instanceof HttpSessionListener))
390 continue;
391 HttpSessionListener listener =
392 (HttpSessionListener) listeners[i];
393 try {
394 fireContainerEvent(context,
395 "beforeSessionCreated",
396 listener);
397 listener.sessionCreated(event);
398 fireContainerEvent(context,
399 "afterSessionCreated",
400 listener);
401 } catch (Throwable t) {
402 try {
403 fireContainerEvent(context,
404 "afterSessionCreated",
405 listener);
406 } catch (Exception e) {
407 ;
408 }
409 manager.getContainer().getLogger().error
410 (sm.getString("standardSession.sessionEvent"), t);
411 }
412 }
413 }
414
415 }
416
417
418 /**
419 * Return descriptive information about this Session implementation and
420 * the corresponding version number, in the format
421 * <code><description>/<version></code>.
422 */
423 public String getInfo() {
424
425 return (info);
426
427 }
428
429
430 /**
431 * Return the last time the client sent a request associated with this
432 * session, as the number of milliseconds since midnight, January 1, 1970
433 * GMT. Actions that your application takes, such as getting or setting
434 * a value associated with the session, do not affect the access time.
435 */
436 public long getLastAccessedTime() {
437
438 if (!isValidInternal()) {
439 throw new IllegalStateException
440 (sm.getString("standardSession.getLastAccessedTime.ise"));
441 }
442
443 return (this.lastAccessedTime);
444 }
445
446 /**
447 * Return the last client access time without invalidation check
448 * @see #getLastAccessedTime().
449 */
450 public long getLastAccessedTimeInternal() {
451 return (this.lastAccessedTime);
452 }
453
454 /**
455 * Return the Manager within which this Session is valid.
456 */
457 public Manager getManager() {
458
459 return (this.manager);
460
461 }
462
463
464 /**
465 * Set the Manager within which this Session is valid.
466 *
467 * @param manager The new Manager
468 */
469 public void setManager(Manager manager) {
470
471 this.manager = manager;
472
473 }
474
475
476 /**
477 * Return the maximum time interval, in seconds, between client requests
478 * before the servlet container will invalidate the session. A negative
479 * time indicates that the session should never time out.
480 */
481 public int getMaxInactiveInterval() {
482
483 return (this.maxInactiveInterval);
484
485 }
486
487
488 /**
489 * Set the maximum time interval, in seconds, between client requests
490 * before the servlet container will invalidate the session. A negative
491 * time indicates that the session should never time out.
492 *
493 * @param interval The new maximum interval
494 */
495 public void setMaxInactiveInterval(int interval) {
496
497 this.maxInactiveInterval = interval;
498 if (isValid && interval == 0) {
499 expire();
500 }
501
502 }
503
504
505 /**
506 * Set the <code>isNew</code> flag for this session.
507 *
508 * @param isNew The new value for the <code>isNew</code> flag
509 */
510 public void setNew(boolean isNew) {
511
512 this.isNew = isNew;
513
514 }
515
516
517 /**
518 * Return the authenticated Principal that is associated with this Session.
519 * This provides an <code>Authenticator</code> with a means to cache a
520 * previously authenticated Principal, and avoid potentially expensive
521 * <code>Realm.authenticate()</code> calls on every request. If there
522 * is no current associated Principal, return <code>null</code>.
523 */
524 public Principal getPrincipal() {
525
526 return (this.principal);
527
528 }
529
530
531 /**
532 * Set the authenticated Principal that is associated with this Session.
533 * This provides an <code>Authenticator</code> with a means to cache a
534 * previously authenticated Principal, and avoid potentially expensive
535 * <code>Realm.authenticate()</code> calls on every request.
536 *
537 * @param principal The new Principal, or <code>null</code> if none
538 */
539 public void setPrincipal(Principal principal) {
540
541 Principal oldPrincipal = this.principal;
542 this.principal = principal;
543 support.firePropertyChange("principal", oldPrincipal, this.principal);
544
545 }
546
547
548 /**
549 * Return the <code>HttpSession</code> for which this object
550 * is the facade.
551 */
552 public HttpSession getSession() {
553
554 if (facade == null){
555 if (SecurityUtil.isPackageProtectionEnabled()){
556 final StandardSession fsession = this;
557 facade = (StandardSessionFacade)AccessController.doPrivileged(new PrivilegedAction(){
558 public Object run(){
559 return new StandardSessionFacade(fsession);
560 }
561 });
562 } else {
563 facade = new StandardSessionFacade(this);
564 }
565 }
566 return (facade);
567
568 }
569
570
571 /**
572 * Return the <code>isValid</code> flag for this session.
573 */
574 public boolean isValid() {
575
576 if (this.expiring) {
577 return true;
578 }
579
580 if (!this.isValid) {
581 return false;
582 }
583
584 if (ACTIVITY_CHECK && accessCount.get() > 0) {
585 return true;
586 }
587
588 if (maxInactiveInterval >= 0) {
589 long timeNow = System.currentTimeMillis();
590 int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
591 if (timeIdle >= maxInactiveInterval) {
592 expire(true);
593 }
594 }
595
596 return (this.isValid);
597 }
598
599
600 /**
601 * Set the <code>isValid</code> flag for this session.
602 *
603 * @param isValid The new value for the <code>isValid</code> flag
604 */
605 public void setValid(boolean isValid) {
606 this.isValid = isValid;
607 }
608
609
610 // ------------------------------------------------- Session Public Methods
611
612
613 /**
614 * Update the accessed time information for this session. This method
615 * should be called by the context when a request comes in for a particular
616 * session, even if the application does not reference it.
617 */
618 public void access() {
619
620 this.lastAccessedTime = this.thisAccessedTime;
621 this.thisAccessedTime = System.currentTimeMillis();
622
623 if (ACTIVITY_CHECK) {
624 accessCount.incrementAndGet();
625 }
626
627 }
628
629
630 /**
631 * End the access.
632 */
633 public void endAccess() {
634
635 isNew = false;
636
637 if (ACTIVITY_CHECK) {
638 accessCount.decrementAndGet();
639 }
640
641 }
642
643
644 /**
645 * Add a session event listener to this component.
646 */
647 public void addSessionListener(SessionListener listener) {
648
649 listeners.add(listener);
650
651 }
652
653
654 /**
655 * Perform the internal processing required to invalidate this session,
656 * without triggering an exception if the session has already expired.
657 */
658 public void expire() {
659
660 expire(true);
661
662 }
663
664
665 /**
666 * Perform the internal processing required to invalidate this session,
667 * without triggering an exception if the session has already expired.
668 *
669 * @param notify Should we notify listeners about the demise of
670 * this session?
671 */
672 public void expire(boolean notify) {
673
674 // Mark this session as "being expired" if needed
675 if (expiring)
676 return;
677
678 synchronized (this) {
679
680 if (manager == null)
681 return;
682
683 expiring = true;
684
685 // Notify interested application event listeners
686 // FIXME - Assumes we call listeners in reverse order
687 Context context = (Context) manager.getContainer();
688 Object listeners[] = context.getApplicationLifecycleListeners();
689 if (notify && (listeners != null)) {
690 HttpSessionEvent event =
691 new HttpSessionEvent(getSession());
692 for (int i = 0; i < listeners.length; i++) {
693 int j = (listeners.length - 1) - i;
694 if (!(listeners[j] instanceof HttpSessionListener))
695 continue;
696 HttpSessionListener listener =
697 (HttpSessionListener) listeners[j];
698 try {
699 fireContainerEvent(context,
700 "beforeSessionDestroyed",
701 listener);
702 listener.sessionDestroyed(event);
703 fireContainerEvent(context,
704 "afterSessionDestroyed",
705 listener);
706 } catch (Throwable t) {
707 try {
708 fireContainerEvent(context,
709 "afterSessionDestroyed",
710 listener);
711 } catch (Exception e) {
712 ;
713 }
714 manager.getContainer().getLogger().error
715 (sm.getString("standardSession.sessionEvent"), t);
716 }
717 }
718 }
719 if (ACTIVITY_CHECK) {
720 accessCount.set(0);
721 }
722 setValid(false);
723
724 /*
725 * Compute how long this session has been alive, and update
726 * session manager's related properties accordingly
727 */
728 long timeNow = System.currentTimeMillis();
729 int timeAlive = (int) ((timeNow - creationTime)/1000);
730 synchronized (manager) {
731 if (timeAlive > manager.getSessionMaxAliveTime()) {
732 manager.setSessionMaxAliveTime(timeAlive);
733 }
734 int numExpired = manager.getExpiredSessions();
735 numExpired++;
736 manager.setExpiredSessions(numExpired);
737 int average = manager.getSessionAverageAliveTime();
738 average = ((average * (numExpired-1)) + timeAlive)/numExpired;
739 manager.setSessionAverageAliveTime(average);
740 }
741
742 // Remove this session from our manager's active sessions
743 manager.remove(this);
744
745 // Notify interested session event listeners
746 if (notify) {
747 fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
748 }
749
750 // We have completed expire of this session
751 expiring = false;
752
753 // Unbind any objects associated with this session
754 String keys[] = keys();
755 for (int i = 0; i < keys.length; i++)
756 removeAttributeInternal(keys[i], notify);
757
758 }
759
760 }
761
762
763 /**
764 * Perform the internal processing required to passivate
765 * this session.
766 */
767 public void passivate() {
768
769 // Notify interested session event listeners
770 fireSessionEvent(Session.SESSION_PASSIVATED_EVENT, null);
771
772 // Notify ActivationListeners
773 HttpSessionEvent event = null;
774 String keys[] = keys();
775 for (int i = 0; i < keys.length; i++) {
776 Object attribute = attributes.get(keys[i]);
777 if (attribute instanceof HttpSessionActivationListener) {
778 if (event == null)
779 event = new HttpSessionEvent(getSession());
780 try {
781 ((HttpSessionActivationListener)attribute)
782 .sessionWillPassivate(event);
783 } catch (Throwable t) {
784 manager.getContainer().getLogger().error
785 (sm.getString("standardSession.attributeEvent"), t);
786 }
787 }
788 }
789
790 }
791
792
793 /**
794 * Perform internal processing required to activate this
795 * session.
796 */
797 public void activate() {
798
799 // Initialize access count
800 if (ACTIVITY_CHECK) {
801 accessCount = new AtomicInteger();
802 }
803
804 // Notify interested session event listeners
805 fireSessionEvent(Session.SESSION_ACTIVATED_EVENT, null);
806
807 // Notify ActivationListeners
808 HttpSessionEvent event = null;
809 String keys[] = keys();
810 for (int i = 0; i < keys.length; i++) {
811 Object attribute = attributes.get(keys[i]);
812 if (attribute instanceof HttpSessionActivationListener) {
813 if (event == null)
814 event = new HttpSessionEvent(getSession());
815 try {
816 ((HttpSessionActivationListener)attribute)
817 .sessionDidActivate(event);
818 } catch (Throwable t) {
819 manager.getContainer().getLogger().error
820 (sm.getString("standardSession.attributeEvent"), t);
821 }
822 }
823 }
824
825 }
826
827
828 /**
829 * Return the object bound with the specified name to the internal notes
830 * for this session, or <code>null</code> if no such binding exists.
831 *
832 * @param name Name of the note to be returned
833 */
834 public Object getNote(String name) {
835
836 return (notes.get(name));
837
838 }
839
840
841 /**
842 * Return an Iterator containing the String names of all notes bindings
843 * that exist for this session.
844 */
845 public Iterator getNoteNames() {
846
847 return (notes.keySet().iterator());
848
849 }
850
851
852 /**
853 * Release all object references, and initialize instance variables, in
854 * preparation for reuse of this object.
855 */
856 public void recycle() {
857
858 // Reset the instance variables associated with this Session
859 attributes.clear();
860 setAuthType(null);
861 creationTime = 0L;
862 expiring = false;
863 id = null;
864 lastAccessedTime = 0L;
865 maxInactiveInterval = -1;
866 notes.clear();
867 setPrincipal(null);
868 isNew = false;
869 isValid = false;
870 manager = null;
871
872 }
873
874
875 /**
876 * Remove any object bound to the specified name in the internal notes
877 * for this session.
878 *
879 * @param name Name of the note to be removed
880 */
881 public void removeNote(String name) {
882
883 notes.remove(name);
884
885 }
886
887
888 /**
889 * Remove a session event listener from this component.
890 */
891 public void removeSessionListener(SessionListener listener) {
892
893 listeners.remove(listener);
894
895 }
896
897
898 /**
899 * Bind an object to a specified name in the internal notes associated
900 * with this session, replacing any existing binding for this name.
901 *
902 * @param name Name to which the object should be bound
903 * @param value Object to be bound to the specified name
904 */
905 public void setNote(String name, Object value) {
906
907 notes.put(name, value);
908
909 }
910
911
912 /**
913 * Return a string representation of this object.
914 */
915 public String toString() {
916
917 StringBuffer sb = new StringBuffer();
918 sb.append("StandardSession[");
919 sb.append(id);
920 sb.append("]");
921 return (sb.toString());
922
923 }
924
925
926 // ------------------------------------------------ Session Package Methods
927
928
929 /**
930 * Read a serialized version of the contents of this session object from
931 * the specified object input stream, without requiring that the
932 * StandardSession itself have been serialized.
933 *
934 * @param stream The object input stream to read from
935 *
936 * @exception ClassNotFoundException if an unknown class is specified
937 * @exception IOException if an input/output error occurs
938 */
939 public void readObjectData(ObjectInputStream stream)
940 throws ClassNotFoundException, IOException {
941
942 readObject(stream);
943
944 }
945
946
947 /**
948 * Write a serialized version of the contents of this session object to
949 * the specified object output stream, without requiring that the
950 * StandardSession itself have been serialized.
951 *
952 * @param stream The object output stream to write to
953 *
954 * @exception IOException if an input/output error occurs
955 */
956 public void writeObjectData(ObjectOutputStream stream)
957 throws IOException {
958
959 writeObject(stream);
960
961 }
962
963
964 // ------------------------------------------------- HttpSession Properties
965
966
967 /**
968 * Return the time when this session was created, in milliseconds since
969 * midnight, January 1, 1970 GMT.
970 *
971 * @exception IllegalStateException if this method is called on an
972 * invalidated session
973 */
974 public long getCreationTime() {
975
976 if (!isValidInternal())
977 throw new IllegalStateException
978 (sm.getString("standardSession.getCreationTime.ise"));
979
980 return (this.creationTime);
981
982 }
983
984
985 /**
986 * Return the ServletContext to which this session belongs.
987 */
988 public ServletContext getServletContext() {
989
990 if (manager == null)
991 return (null);
992 Context context = (Context) manager.getContainer();
993 if (context == null)
994 return (null);
995 else
996 return (context.getServletContext());
997
998 }
999
1000
1001 /**
1002 * Return the session context with which this session is associated.
1003 *
1004 * @deprecated As of Version 2.1, this method is deprecated and has no
1005 * replacement. It will be removed in a future version of the
1006 * Java Servlet API.
1007 */
1008 public HttpSessionContext getSessionContext() {
1009
1010 if (sessionContext == null)
1011 sessionContext = new StandardSessionContext();
1012 return (sessionContext);
1013
1014 }
1015
1016
1017 // ----------------------------------------------HttpSession Public Methods
1018
1019
1020 /**
1021 * Return the object bound with the specified name in this session, or
1022 * <code>null</code> if no object is bound with that name.
1023 *
1024 * @param name Name of the attribute to be returned
1025 *
1026 * @exception IllegalStateException if this method is called on an
1027 * invalidated session
1028 */
1029 public Object getAttribute(String name) {
1030
1031 if (!isValidInternal())
1032 throw new IllegalStateException
1033 (sm.getString("standardSession.getAttribute.ise"));
1034
1035 return (attributes.get(name));
1036
1037 }
1038
1039
1040 /**
1041 * Return an <code>Enumeration</code> of <code>String</code> objects
1042 * containing the names of the objects bound to this session.
1043 *
1044 * @exception IllegalStateException if this method is called on an
1045 * invalidated session
1046 */
1047 public Enumeration getAttributeNames() {
1048
1049 if (!isValidInternal())
1050 throw new IllegalStateException
1051 (sm.getString("standardSession.getAttributeNames.ise"));
1052
1053 return (new Enumerator(attributes.keySet(), true));
1054
1055 }
1056
1057
1058 /**
1059 * Return the object bound with the specified name in this session, or
1060 * <code>null</code> if no object is bound with that name.
1061 *
1062 * @param name Name of the value to be returned
1063 *
1064 * @exception IllegalStateException if this method is called on an
1065 * invalidated session
1066 *
1067 * @deprecated As of Version 2.2, this method is replaced by
1068 * <code>getAttribute()</code>
1069 */
1070 public Object getValue(String name) {
1071
1072 return (getAttribute(name));
1073
1074 }
1075
1076
1077 /**
1078 * Return the set of names of objects bound to this session. If there
1079 * are no such objects, a zero-length array is returned.
1080 *
1081 * @exception IllegalStateException if this method is called on an
1082 * invalidated session
1083 *
1084 * @deprecated As of Version 2.2, this method is replaced by
1085 * <code>getAttributeNames()</code>
1086 */
1087 public String[] getValueNames() {
1088
1089 if (!isValidInternal())
1090 throw new IllegalStateException
1091 (sm.getString("standardSession.getValueNames.ise"));
1092
1093 return (keys());
1094
1095 }
1096
1097
1098 /**
1099 * Invalidates this session and unbinds any objects bound to it.
1100 *
1101 * @exception IllegalStateException if this method is called on
1102 * an invalidated session
1103 */
1104 public void invalidate() {
1105
1106 if (!isValidInternal())
1107 throw new IllegalStateException
1108 (sm.getString("standardSession.invalidate.ise"));
1109
1110 // Cause this session to expire
1111 expire();
1112
1113 }
1114
1115
1116 /**
1117 * Return <code>true</code> if the client does not yet know about the
1118 * session, or if the client chooses not to join the session. For
1119 * example, if the server used only cookie-based sessions, and the client
1120 * has disabled the use of cookies, then a session would be new on each
1121 * request.
1122 *
1123 * @exception IllegalStateException if this method is called on an
1124 * invalidated session
1125 */
1126 public boolean isNew() {
1127
1128 if (!isValidInternal())
1129 throw new IllegalStateException
1130 (sm.getString("standardSession.isNew.ise"));
1131
1132 return (this.isNew);
1133
1134 }
1135
1136
1137
1138 /**
1139 * Bind an object to this session, using the specified name. If an object
1140 * of the same name is already bound to this session, the object is
1141 * replaced.
1142 * <p>
1143 * After this method executes, and if the object implements
1144 * <code>HttpSessionBindingListener</code>, the container calls
1145 * <code>valueBound()</code> on the object.
1146 *
1147 * @param name Name to which the object is bound, cannot be null
1148 * @param value Object to be bound, cannot be null
1149 *
1150 * @exception IllegalStateException if this method is called on an
1151 * invalidated session
1152 *
1153 * @deprecated As of Version 2.2, this method is replaced by
1154 * <code>setAttribute()</code>
1155 */
1156 public void putValue(String name, Object value) {
1157
1158 setAttribute(name, value);
1159
1160 }
1161
1162
1163 /**
1164 * Remove the object bound with the specified name from this session. If
1165 * the session does not have an object bound with this name, this method
1166 * does nothing.
1167 * <p>
1168 * After this method executes, and if the object implements
1169 * <code>HttpSessionBindingListener</code>, the container calls
1170 * <code>valueUnbound()</code> on the object.
1171 *
1172 * @param name Name of the object to remove from this session.
1173 *
1174 * @exception IllegalStateException if this method is called on an
1175 * invalidated session
1176 */
1177 public void removeAttribute(String name) {
1178
1179 removeAttribute(name, true);
1180
1181 }
1182
1183
1184 /**
1185 * Remove the object bound with the specified name from this session. If
1186 * the session does not have an object bound with this name, this method
1187 * does nothing.
1188 * <p>
1189 * After this method executes, and if the object implements
1190 * <code>HttpSessionBindingListener</code>, the container calls
1191 * <code>valueUnbound()</code> on the object.
1192 *
1193 * @param name Name of the object to remove from this session.
1194 * @param notify Should we notify interested listeners that this
1195 * attribute is being removed?
1196 *
1197 * @exception IllegalStateException if this method is called on an
1198 * invalidated session
1199 */
1200 public void removeAttribute(String name, boolean notify) {
1201
1202 // Validate our current state
1203 if (!isValidInternal())
1204 throw new IllegalStateException
1205 (sm.getString("standardSession.removeAttribute.ise"));
1206
1207 removeAttributeInternal(name, notify);
1208
1209 }
1210
1211
1212 /**
1213 * Remove the object bound with the specified name from this session. If
1214 * the session does not have an object bound with this name, this method
1215 * does nothing.
1216 * <p>
1217 * After this method executes, and if the object implements
1218 * <code>HttpSessionBindingListener</code>, the container calls
1219 * <code>valueUnbound()</code> on the object.
1220 *
1221 * @param name Name of the object to remove from this session.
1222 *
1223 * @exception IllegalStateException if this method is called on an
1224 * invalidated session
1225 *
1226 * @deprecated As of Version 2.2, this method is replaced by
1227 * <code>removeAttribute()</code>
1228 */
1229 public void removeValue(String name) {
1230
1231 removeAttribute(name);
1232
1233 }
1234
1235
1236 /**
1237 * Bind an object to this session, using the specified name. If an object
1238 * of the same name is already bound to this session, the object is
1239 * replaced.
1240 * <p>
1241 * After this method executes, and if the object implements
1242 * <code>HttpSessionBindingListener</code>, the container calls
1243 * <code>valueBound()</code> on the object.
1244 *
1245 * @param name Name to which the object is bound, cannot be null
1246 * @param value Object to be bound, cannot be null
1247 *
1248 * @exception IllegalArgumentException if an attempt is made to add a
1249 * non-serializable object in an environment marked distributable.
1250 * @exception IllegalStateException if this method is called on an
1251 * invalidated session
1252 */
1253 public void setAttribute(String name, Object value) {
1254 setAttribute(name,value,true);
1255 }
1256 /**
1257 * Bind an object to this session, using the specified name. If an object
1258 * of the same name is already bound to this session, the object is
1259 * replaced.
1260 * <p>
1261 * After this method executes, and if the object implements
1262 * <code>HttpSessionBindingListener</code>, the container calls
1263 * <code>valueBound()</code> on the object.
1264 *
1265 * @param name Name to which the object is bound, cannot be null
1266 * @param value Object to be bound, cannot be null
1267 * @param notify whether to notify session listeners
1268 * @exception IllegalArgumentException if an attempt is made to add a
1269 * non-serializable object in an environment marked distributable.
1270 * @exception IllegalStateException if this method is called on an
1271 * invalidated session
1272 */
1273
1274 public void setAttribute(String name, Object value, boolean notify) {
1275
1276 // Name cannot be null
1277 if (name == null)
1278 throw new IllegalArgumentException
1279 (sm.getString("standardSession.setAttribute.namenull"));
1280
1281 // Null value is the same as removeAttribute()
1282 if (value == null) {
1283 removeAttribute(name);
1284 return;
1285 }
1286
1287 // Validate our current state
1288 if (!isValidInternal())
1289 throw new IllegalStateException
1290 (sm.getString("standardSession.setAttribute.ise"));
1291 if ((manager != null) && manager.getDistributable() &&
1292 !(value instanceof Serializable))
1293 throw new IllegalArgumentException
1294 (sm.getString("standardSession.setAttribute.iae"));
1295
1296 // Construct an event with the new value
1297 HttpSessionBindingEvent event = null;
1298
1299 // Call the valueBound() method if necessary
1300 if (notify && value instanceof HttpSessionBindingListener) {
1301 // Don't call any notification if replacing with the same value
1302 Object oldValue = attributes.get(name);
1303 if (value != oldValue) {
1304 event = new HttpSessionBindingEvent(getSession(), name, value);
1305 try {
1306 ((HttpSessionBindingListener) value).valueBound(event);
1307 } catch (Throwable t){
1308 manager.getContainer().getLogger().error
1309 (sm.getString("standardSession.bindingEvent"), t);
1310 }
1311 }
1312 }
1313
1314 // Replace or add this attribute
1315 Object unbound = attributes.put(name, value);
1316
1317 // Call the valueUnbound() method if necessary
1318 if (notify && (unbound != null) && (unbound != value) &&
1319 (unbound instanceof HttpSessionBindingListener)) {
1320 try {
1321 ((HttpSessionBindingListener) unbound).valueUnbound
1322 (new HttpSessionBindingEvent(getSession(), name));
1323 } catch (Throwable t) {
1324 manager.getContainer().getLogger().error
1325 (sm.getString("standardSession.bindingEvent"), t);
1326 }
1327 }
1328
1329 if ( !notify ) return;
1330
1331 // Notify interested application event listeners
1332 Context context = (Context) manager.getContainer();
1333 Object listeners[] = context.getApplicationEventListeners();
1334 if (listeners == null)
1335 return;
1336 for (int i = 0; i < listeners.length; i++) {
1337 if (!(listeners[i] instanceof HttpSessionAttributeListener))
1338 continue;
1339 HttpSessionAttributeListener listener =
1340 (HttpSessionAttributeListener) listeners[i];
1341 try {
1342 if (unbound != null) {
1343 fireContainerEvent(context,
1344 "beforeSessionAttributeReplaced",
1345 listener);
1346 if (event == null) {
1347 event = new HttpSessionBindingEvent
1348 (getSession(), name, unbound);
1349 }
1350 listener.attributeReplaced(event);
1351 fireContainerEvent(context,
1352 "afterSessionAttributeReplaced",
1353 listener);
1354 } else {
1355 fireContainerEvent(context,
1356 "beforeSessionAttributeAdded",
1357 listener);
1358 if (event == null) {
1359 event = new HttpSessionBindingEvent
1360 (getSession(), name, value);
1361 }
1362 listener.attributeAdded(event);
1363 fireContainerEvent(context,
1364 "afterSessionAttributeAdded",
1365 listener);
1366 }
1367 } catch (Throwable t) {
1368 try {
1369 if (unbound != null) {
1370 fireContainerEvent(context,
1371 "afterSessionAttributeReplaced",
1372 listener);
1373 } else {
1374 fireContainerEvent(context,
1375 "afterSessionAttributeAdded",
1376 listener);
1377 }
1378 } catch (Exception e) {
1379 ;
1380 }
1381 manager.getContainer().getLogger().error
1382 (sm.getString("standardSession.attributeEvent"), t);
1383 }
1384 }
1385
1386 }
1387
1388
1389 // ------------------------------------------ HttpSession Protected Methods
1390
1391
1392 /**
1393 * Return the <code>isValid</code> flag for this session without any expiration
1394 * check.
1395 */
1396 protected boolean isValidInternal() {
1397 return (this.isValid || this.expiring);
1398 }
1399
1400
1401 /**
1402 * Read a serialized version of this session object from the specified
1403 * object input stream.
1404 * <p>
1405 * <b>IMPLEMENTATION NOTE</b>: The reference to the owning Manager
1406 * is not restored by this method, and must be set explicitly.
1407 *
1408 * @param stream The input stream to read from
1409 *
1410 * @exception ClassNotFoundException if an unknown class is specified
1411 * @exception IOException if an input/output error occurs
1412 */
1413 protected void readObject(ObjectInputStream stream)
1414 throws ClassNotFoundException, IOException {
1415
1416 // Deserialize the scalar instance variables (except Manager)
1417 authType = null; // Transient only
1418 creationTime = ((Long) stream.readObject()).longValue();
1419 lastAccessedTime = ((Long) stream.readObject()).longValue();
1420 maxInactiveInterval = ((Integer) stream.readObject()).intValue();
1421 isNew = ((Boolean) stream.readObject()).booleanValue();
1422 isValid = ((Boolean) stream.readObject()).booleanValue();
1423 thisAccessedTime = ((Long) stream.readObject()).longValue();
1424 principal = null; // Transient only
1425 // setId((String) stream.readObject());
1426 id = (String) stream.readObject();
1427 if (manager.getContainer().getLogger().isDebugEnabled())
1428 manager.getContainer().getLogger().debug
1429 ("readObject() loading session " + id);
1430
1431 // Deserialize the attribute count and attribute values
1432 if (attributes == null)
1433 attributes = new Hashtable();
1434 int n = ((Integer) stream.readObject()).intValue();
1435 boolean isValidSave = isValid;
1436 isValid = true;
1437 for (int i = 0; i < n; i++) {
1438 String name = (String) stream.readObject();
1439 Object value = (Object) stream.readObject();
1440 if ((value instanceof String) && (value.equals(NOT_SERIALIZED)))
1441 continue;
1442 if (manager.getContainer().getLogger().isDebugEnabled())
1443 manager.getContainer().getLogger().debug(" loading attribute '" + name +
1444 "' with value '" + value + "'");
1445 attributes.put(name, value);
1446 }
1447 isValid = isValidSave;
1448
1449 if (listeners == null) {
1450 listeners = new ArrayList();
1451 }
1452
1453 if (notes == null) {
1454 notes = new Hashtable();
1455 }
1456 }
1457
1458
1459 /**
1460 * Write a serialized version of this session object to the specified
1461 * object output stream.
1462 * <p>
1463 * <b>IMPLEMENTATION NOTE</b>: The owning Manager will not be stored
1464 * in the serialized representation of this Session. After calling
1465 * <code>readObject()</code>, you must set the associated Manager
1466 * explicitly.
1467 * <p>
1468 * <b>IMPLEMENTATION NOTE</b>: Any attribute that is not Serializable
1469 * will be unbound from the session, with appropriate actions if it
1470 * implements HttpSessionBindingListener. If you do not want any such
1471 * attributes, be sure the <code>distributable</code> property of the
1472 * associated Manager is set to <code>true</code>.
1473 *
1474 * @param stream The output stream to write to
1475 *
1476 * @exception IOException if an input/output error occurs
1477 */
1478 protected void writeObject(ObjectOutputStream stream) throws IOException {
1479
1480 // Write the scalar instance variables (except Manager)
1481 stream.writeObject(new Long(creationTime));
1482 stream.writeObject(new Long(lastAccessedTime));
1483 stream.writeObject(new Integer(maxInactiveInterval));
1484 stream.writeObject(new Boolean(isNew));
1485 stream.writeObject(new Boolean(isValid));
1486 stream.writeObject(new Long(thisAccessedTime));
1487 stream.writeObject(id);
1488 if (manager.getContainer().getLogger().isDebugEnabled())
1489 manager.getContainer().getLogger().debug
1490 ("writeObject() storing session " + id);
1491
1492 // Accumulate the names of serializable and non-serializable attributes
1493 String keys[] = keys();
1494 ArrayList saveNames = new ArrayList();
1495 ArrayList saveValues = new ArrayList();
1496 for (int i = 0; i < keys.length; i++) {
1497 Object value = attributes.get(keys[i]);
1498 if (value == null)
1499 continue;
1500 else if ( (value instanceof Serializable)
1501 && (!exclude(keys[i]) )) {
1502 saveNames.add(keys[i]);
1503 saveValues.add(value);
1504 } else {
1505 removeAttributeInternal(keys[i], true);
1506 }
1507 }
1508
1509 // Serialize the attribute count and the Serializable attributes
1510 int n = saveNames.size();
1511 stream.writeObject(new Integer(n));
1512 for (int i = 0; i < n; i++) {
1513 stream.writeObject((String) saveNames.get(i));
1514 try {
1515 stream.writeObject(saveValues.get(i));
1516 if (manager.getContainer().getLogger().isDebugEnabled())
1517 manager.getContainer().getLogger().debug
1518 (" storing attribute '" + saveNames.get(i) +
1519 "' with value '" + saveValues.get(i) + "'");
1520 } catch (NotSerializableException e) {
1521 manager.getContainer().getLogger().warn
1522 (sm.getString("standardSession.notSerializable",
1523 saveNames.get(i), id), e);
1524 stream.writeObject(NOT_SERIALIZED);
1525 if (manager.getContainer().getLogger().isDebugEnabled())
1526 manager.getContainer().getLogger().debug
1527 (" storing attribute '" + saveNames.get(i) +
1528 "' with value NOT_SERIALIZED");
1529 }
1530 }
1531
1532 }
1533
1534
1535 /**
1536 * Exclude attribute that cannot be serialized.
1537 * @param name the attribute's name
1538 */
1539 protected boolean exclude(String name){
1540
1541 for (int i = 0; i < excludedAttributes.length; i++) {
1542 if (name.equalsIgnoreCase(excludedAttributes[i]))
1543 return true;
1544 }
1545
1546 return false;
1547 }
1548
1549
1550 // ------------------------------------------------------ Protected Methods
1551
1552
1553 /**
1554 * Fire container events if the Context implementation is the
1555 * <code>org.apache.catalina.core.StandardContext</code>.
1556 *
1557 * @param context Context for which to fire events
1558 * @param type Event type
1559 * @param data Event data
1560 *
1561 * @exception Exception occurred during event firing
1562 */
1563 protected void fireContainerEvent(Context context,
1564 String type, Object data)
1565 throws Exception {
1566
1567 if (!"org.apache.catalina.core.StandardContext".equals
1568 (context.getClass().getName())) {
1569 return; // Container events are not supported
1570 }
1571 // NOTE: Race condition is harmless, so do not synchronize
1572 if (containerEventMethod == null) {
1573 containerEventMethod =
1574 context.getClass().getMethod("fireContainerEvent",
1575 containerEventTypes);
1576 }
1577 Object containerEventParams[] = new Object[2];
1578 containerEventParams[0] = type;
1579 containerEventParams[1] = data;
1580 containerEventMethod.invoke(context, containerEventParams);
1581
1582 }
1583
1584
1585
1586 /**
1587 * Notify all session event listeners that a particular event has
1588 * occurred for this Session. The default implementation performs
1589 * this notification synchronously using the calling thread.
1590 *
1591 * @param type Event type
1592 * @param data Event data
1593 */
1594 public void fireSessionEvent(String type, Object data) {
1595 if (listeners.size() < 1)
1596 return;
1597 SessionEvent event = new SessionEvent(this, type, data);
1598 SessionListener list[] = new SessionListener[0];
1599 synchronized (listeners) {
1600 list = (SessionListener[]) listeners.toArray(list);
1601 }
1602
1603 for (int i = 0; i < list.length; i++){
1604 ((SessionListener) list[i]).sessionEvent(event);
1605 }
1606
1607 }
1608
1609
1610 /**
1611 * Return the names of all currently defined session attributes
1612 * as an array of Strings. If there are no defined attributes, a
1613 * zero-length array is returned.
1614 */
1615 protected String[] keys() {
1616
1617 return ((String[]) attributes.keySet().toArray(EMPTY_ARRAY));
1618
1619 }
1620
1621
1622 /**
1623 * Remove the object bound with the specified name from this session. If
1624 * the session does not have an object bound with this name, this method
1625 * does nothing.
1626 * <p>
1627 * After this method executes, and if the object implements
1628 * <code>HttpSessionBindingListener</code>, the container calls
1629 * <code>valueUnbound()</code> on the object.
1630 *
1631 * @param name Name of the object to remove from this session.
1632 * @param notify Should we notify interested listeners that this
1633 * attribute is being removed?
1634 */
1635 protected void removeAttributeInternal(String name, boolean notify) {
1636
1637 // Remove this attribute from our collection
1638 Object value = attributes.remove(name);
1639
1640 // Do we need to do valueUnbound() and attributeRemoved() notification?
1641 if (!notify || (value == null)) {
1642 return;
1643 }
1644
1645 // Call the valueUnbound() method if necessary
1646 HttpSessionBindingEvent event = null;
1647 if (value instanceof HttpSessionBindingListener) {
1648 event = new HttpSessionBindingEvent(getSession(), name, value);
1649 ((HttpSessionBindingListener) value).valueUnbound(event);
1650 }
1651
1652 // Notify interested application event listeners
1653 Context context = (Context) manager.getContainer();
1654 Object listeners[] = context.getApplicationEventListeners();
1655 if (listeners == null)
1656 return;
1657 for (int i = 0; i < listeners.length; i++) {
1658 if (!(listeners[i] instanceof HttpSessionAttributeListener))
1659 continue;
1660 HttpSessionAttributeListener listener =
1661 (HttpSessionAttributeListener) listeners[i];
1662 try {
1663 fireContainerEvent(context,
1664 "beforeSessionAttributeRemoved",
1665 listener);
1666 if (event == null) {
1667 event = new HttpSessionBindingEvent
1668 (getSession(), name, value);
1669 }
1670 listener.attributeRemoved(event);
1671 fireContainerEvent(context,
1672 "afterSessionAttributeRemoved",
1673 listener);
1674 } catch (Throwable t) {
1675 try {
1676 fireContainerEvent(context,
1677 "afterSessionAttributeRemoved",
1678 listener);
1679 } catch (Exception e) {
1680 ;
1681 }
1682 manager.getContainer().getLogger().error
1683 (sm.getString("standardSession.attributeEvent"), t);
1684 }
1685 }
1686
1687 }
1688
1689
1690 }
1691
1692
1693 // ------------------------------------------------------------ Protected Class
1694
1695
1696 /**
1697 * This class is a dummy implementation of the <code>HttpSessionContext</code>
1698 * interface, to conform to the requirement that such an object be returned
1699 * when <code>HttpSession.getSessionContext()</code> is called.
1700 *
1701 * @author Craig R. McClanahan
1702 *
1703 * @deprecated As of Java Servlet API 2.1 with no replacement. The
1704 * interface will be removed in a future version of this API.
1705 */
1706
1707 final class StandardSessionContext implements HttpSessionContext {
1708
1709
1710 protected HashMap dummy = new HashMap();
1711
1712 /**
1713 * Return the session identifiers of all sessions defined
1714 * within this context.
1715 *
1716 * @deprecated As of Java Servlet API 2.1 with no replacement.
1717 * This method must return an empty <code>Enumeration</code>
1718 * and will be removed in a future version of the API.
1719 */
1720 public Enumeration getIds() {
1721
1722 return (new Enumerator(dummy));
1723
1724 }
1725
1726
1727 /**
1728 * Return the <code>HttpSession</code> associated with the
1729 * specified session identifier.
1730 *
1731 * @param id Session identifier for which to look up a session
1732 *
1733 * @deprecated As of Java Servlet API 2.1 with no replacement.
1734 * This method must return null and will be removed in a
1735 * future version of the API.
1736 */
1737 public HttpSession getSession(String id) {
1738
1739 return (null);
1740
1741 }
1742
1743
1744
1745 }