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(thistrue);
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 == nullreturn 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 trueif 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 == nullreturn;
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