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