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