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
20 import java.beans.PropertyChangeListener;
21 import java.beans.PropertyChangeSupport;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Date;
25 import java.util.Deque;
26 import java.util.Enumeration;
27 import java.util.HashMap;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.concurrent.ConcurrentHashMap;
32 import java.util.concurrent.atomic.AtomicLong;
33 import java.util.regex.Pattern;
34 import java.util.regex.PatternSyntaxException;
35
36 import org.apache.catalina.Container;
37 import org.apache.catalina.Context;
38 import org.apache.catalina.Engine;
39 import org.apache.catalina.Globals;
40 import org.apache.catalina.Lifecycle;
41 import org.apache.catalina.LifecycleException;
42 import org.apache.catalina.LifecycleState;
43 import org.apache.catalina.Manager;
44 import org.apache.catalina.Session;
45 import org.apache.catalina.SessionIdGenerator;
46 import org.apache.catalina.util.LifecycleMBeanBase;
47 import org.apache.catalina.util.SessionIdGeneratorBase;
48 import org.apache.catalina.util.StandardSessionIdGenerator;
49 import org.apache.catalina.util.ToStringUtil;
50 import org.apache.juli.logging.Log;
51 import org.apache.juli.logging.LogFactory;
52 import org.apache.tomcat.util.res.StringManager;
53
54
55 /**
56  * Minimal implementation of the <b>Manager</b> interface that supports
57  * no session persistence or distributable capabilities.  This class may
58  * be subclassed to create more sophisticated Manager implementations.
59  *
60  * @author Craig R. McClanahan
61  */

62 public abstract class ManagerBase extends LifecycleMBeanBase implements Manager {
63
64     private final Log log = LogFactory.getLog(ManagerBase.class); // must not be static
65
66     // ----------------------------------------------------- Instance Variables
67
68     /**
69      * The Context with which this Manager is associated.
70      */

71     private Context context;
72
73
74     /**
75      * The descriptive name of this Manager implementation (for logging).
76      */

77     private static final String name = "ManagerBase";
78
79
80     /**
81      * The Java class name of the secure random number generator class to be
82      * used when generating session identifiers. The random number generator
83      * class must be self-seeding and have a zero-argument constructor. If not
84      * specified, an instance of {@link java.security.SecureRandom} will be
85      * generated.
86      */

87     protected String secureRandomClass = null;
88
89     /**
90      * The name of the algorithm to use to create instances of
91      * {@link java.security.SecureRandom} which are used to generate session IDs.
92      * If no algorithm is specified, SHA1PRNG is used. To use the platform
93      * default (which may be SHA1PRNG), specify the empty string. If an invalid
94      * algorithm and/or provider is specified the SecureRandom instances will be
95      * created using the defaults. If that fails, the SecureRandom instances
96      * will be created using platform defaults.
97      */

98     protected String secureRandomAlgorithm = "SHA1PRNG";
99
100     /**
101      * The name of the provider to use to create instances of
102      * {@link java.security.SecureRandom} which are used to generate session IDs.
103      * If no algorithm is specified the of SHA1PRNG default is used. If an
104      * invalid algorithm and/or provider is specified the SecureRandom instances
105      * will be created using the defaults. If that fails, the SecureRandom
106      * instances will be created using platform defaults.
107      */

108     protected String secureRandomProvider = null;
109
110     protected SessionIdGenerator sessionIdGenerator = null;
111     protected Class<? extends SessionIdGenerator> sessionIdGeneratorClass = null;
112
113     /**
114      * The longest time (in seconds) that an expired session had been alive.
115      */

116     protected volatile int sessionMaxAliveTime;
117     private final Object sessionMaxAliveTimeUpdateLock = new Object();
118
119
120     protected static final int TIMING_STATS_CACHE_SIZE = 100;
121
122     protected final Deque<SessionTiming> sessionCreationTiming =
123             new LinkedList<>();
124
125     protected final Deque<SessionTiming> sessionExpirationTiming =
126             new LinkedList<>();
127
128     /**
129      * Number of sessions that have expired.
130      */

131     protected final AtomicLong expiredSessions = new AtomicLong(0);
132
133
134     /**
135      * The set of currently active Sessions for this Manager, keyed by
136      * session identifier.
137      */

138     protected Map<String, Session> sessions = new ConcurrentHashMap<>();
139
140     // Number of sessions created by this manager
141     protected long sessionCounter=0;
142
143     protected volatile int maxActive=0;
144
145     private final Object maxActiveUpdateLock = new Object();
146
147     /**
148      * The maximum number of active Sessions allowed, or -1 for no limit.
149      */

150     protected int maxActiveSessions = -1;
151
152     /**
153      * Number of session creations that failed due to maxActiveSessions.
154      */

155     protected int rejectedSessions = 0;
156
157     // number of duplicated session ids - anything >0 means we have problems
158     protected volatile int duplicates=0;
159
160     /**
161      * Processing time during session expiration.
162      */

163     protected long processingTime = 0;
164
165     /**
166      * Iteration count for background processing.
167      */

168     private int count = 0;
169
170
171     /**
172      * Frequency of the session expiration, and related manager operations.
173      * Manager operations will be done once for the specified amount of
174      * backgroundProcess calls (ie, the lower the amount, the most often the
175      * checks will occur).
176      */

177     protected int processExpiresFrequency = 6;
178
179     /**
180      * The string manager for this package.
181      */

182     protected static final StringManager sm = StringManager.getManager(ManagerBase.class);
183
184     /**
185      * The property change support for this component.
186      */

187     protected final PropertyChangeSupport support =
188             new PropertyChangeSupport(this);
189
190     private Pattern sessionAttributeNamePattern;
191
192     private Pattern sessionAttributeValueClassNamePattern;
193
194     private boolean warnOnSessionAttributeFilterFailure;
195
196     private boolean notifyBindingListenerOnUnchangedValue;
197
198     private boolean notifyAttributeListenerOnUnchangedValue = true;
199
200     /**
201      * Determines whether sessions managed by this manager shall persist (serialize)
202      * authentication information or not.
203      */

204     private boolean persistAuthentication = false;
205
206
207     // ------------------------------------------------------------ Constructors
208
209     public ManagerBase() {
210         if (Globals.IS_SECURITY_ENABLED) {
211             // Minimum set required for default distribution/persistence to work
212             // plus String
213             // plus SerializablePrincipal and String[] (required for authentication persistence)
214             setSessionAttributeValueClassNameFilter(
215                     "java\\.lang\\.(?:Boolean|Integer|Long|Number|String)"
216                     + "|org\\.apache\\.catalina\\.realm\\.GenericPrincipal\\$SerializablePrincipal"
217                     + "|\\[Ljava.lang.String;");
218             setWarnOnSessionAttributeFilterFailure(true);
219         }
220     }
221
222
223     // -------------------------------------------------------------- Properties
224
225     @Override
226     public boolean getNotifyAttributeListenerOnUnchangedValue() {
227         return notifyAttributeListenerOnUnchangedValue;
228     }
229
230
231
232     @Override
233     public void setNotifyAttributeListenerOnUnchangedValue(boolean notifyAttributeListenerOnUnchangedValue) {
234         this.notifyAttributeListenerOnUnchangedValue = notifyAttributeListenerOnUnchangedValue;
235     }
236
237
238     @Override
239     public boolean getNotifyBindingListenerOnUnchangedValue() {
240         return notifyBindingListenerOnUnchangedValue;
241     }
242
243
244     @Override
245     public void setNotifyBindingListenerOnUnchangedValue(boolean notifyBindingListenerOnUnchangedValue) {
246         this.notifyBindingListenerOnUnchangedValue = notifyBindingListenerOnUnchangedValue;
247     }
248
249
250     /**
251      * Obtain the regular expression used to filter session attribute based on
252      * attribute name. The regular expression is anchored so it must match the
253      * entire name
254      *
255      * @return The regular expression currently used to filter attribute names.
256      *         {@code null} means no filter is applied. If an empty string is
257      *         specified then no names will match the filter and all attributes
258      *         will be blocked.
259      */

260     public String getSessionAttributeNameFilter() {
261         if (sessionAttributeNamePattern == null) {
262             return null;
263         }
264         return sessionAttributeNamePattern.toString();
265     }
266
267
268     /**
269      * Set the regular expression to use to filter session attributes based on
270      * attribute name. The regular expression is anchored so it must match the
271      * entire name.
272      *
273      * @param sessionAttributeNameFilter The regular expression to use to filter
274      *        session attributes based on attribute name. Use {@code nullif no
275      *        filtering is required. If an empty string is specified then no
276      *        names will match the filter and all attributes will be blocked.
277      *
278      * @throws PatternSyntaxException If the expression is not valid
279      */

280     public void setSessionAttributeNameFilter(String sessionAttributeNameFilter)
281             throws PatternSyntaxException {
282         if (sessionAttributeNameFilter == null || sessionAttributeNameFilter.length() == 0) {
283             sessionAttributeNamePattern = null;
284         } else {
285             sessionAttributeNamePattern = Pattern.compile(sessionAttributeNameFilter);
286         }
287     }
288
289
290     /**
291      * Provides {@link #getSessionAttributeNameFilter()} as a pre-compiled
292      * regular expression pattern.
293      *
294      * @return The pre-compiled pattern used to filter session attributes based
295      *         on attribute name. {@code null} means no filter is applied.
296      */

297     protected Pattern getSessionAttributeNamePattern() {
298         return sessionAttributeNamePattern;
299     }
300
301
302     /**
303      * Obtain the regular expression used to filter session attribute based on
304      * the implementation class of the value. The regular expression is anchored
305      * and must match the fully qualified class name.
306      *
307      * @return The regular expression currently used to filter class names.
308      *         {@code null} means no filter is applied. If an empty string is
309      *         specified then no names will match the filter and all attributes
310      *         will be blocked.
311      */

312     public String getSessionAttributeValueClassNameFilter() {
313         if (sessionAttributeValueClassNamePattern == null) {
314             return null;
315         }
316         return sessionAttributeValueClassNamePattern.toString();
317     }
318
319
320     /**
321      * Provides {@link #getSessionAttributeValueClassNameFilter()} as a
322      * pre-compiled regular expression pattern.
323      *
324      * @return The pre-compiled pattern used to filter session attributes based
325      *         on the implementation class name of the value. {@code null} means
326      *         no filter is applied.
327      */

328     protected Pattern getSessionAttributeValueClassNamePattern() {
329         return sessionAttributeValueClassNamePattern;
330     }
331
332
333     /**
334      * Set the regular expression to use to filter classes used for session
335      * attributes. The regular expression is anchored and must match the fully
336      * qualified class name.
337      *
338      * @param sessionAttributeValueClassNameFilter The regular expression to use
339      *            to filter session attributes based on class name. Use {@code
340      *            nullif no filtering is required. If an empty string is
341      *           specified then no names will match the filter and all
342      *           attributes will be blocked.
343      *
344      * @throws PatternSyntaxException If the expression is not valid
345      */

346     public void setSessionAttributeValueClassNameFilter(String sessionAttributeValueClassNameFilter)
347             throws PatternSyntaxException {
348         if (sessionAttributeValueClassNameFilter == null ||
349                 sessionAttributeValueClassNameFilter.length() == 0) {
350             sessionAttributeValueClassNamePattern = null;
351         } else {
352             sessionAttributeValueClassNamePattern =
353                     Pattern.compile(sessionAttributeValueClassNameFilter);
354         }
355     }
356
357
358     /**
359      * Should a warn level log message be generated if a session attribute is
360      * not persisted / replicated / restored.
361      *
362      * @return {@code trueif a warn level log message should be generated
363      */

364     public boolean getWarnOnSessionAttributeFilterFailure() {
365         return warnOnSessionAttributeFilterFailure;
366     }
367
368
369     /**
370      * Configure whether or not a warn level log message should be generated if
371      * a session attribute is not persisted / replicated / restored.
372      *
373      * @param warnOnSessionAttributeFilterFailure {@code trueif the
374      *            warn level message should be generated
375      *
376      */

377     public void setWarnOnSessionAttributeFilterFailure(
378             boolean warnOnSessionAttributeFilterFailure) {
379         this.warnOnSessionAttributeFilterFailure = warnOnSessionAttributeFilterFailure;
380     }
381
382
383     @Override
384     public Context getContext() {
385         return context;
386     }
387
388
389     @Override
390     public void setContext(Context context) {
391         if (this.context == context) {
392             // NO-OP
393             return;
394         }
395         if (!getState().equals(LifecycleState.NEW)) {
396             throw new IllegalStateException(sm.getString("managerBase.setContextNotNew"));
397         }
398         Context oldContext = this.context;
399         this.context = context;
400         support.firePropertyChange("context", oldContext, this.context);
401     }
402
403
404     /**
405      * @return The name of the implementation class.
406      */

407     public String getClassName() {
408         return this.getClass().getName();
409     }
410
411
412     @Override
413     public SessionIdGenerator getSessionIdGenerator() {
414         if (sessionIdGenerator != null) {
415             return sessionIdGenerator;
416         } else if (sessionIdGeneratorClass != null) {
417             try {
418                 sessionIdGenerator = sessionIdGeneratorClass.getConstructor().newInstance();
419                 return sessionIdGenerator;
420             } catch(ReflectiveOperationException ex) {
421                 // Ignore
422             }
423         }
424         return null;
425     }
426
427
428     @Override
429     public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
430         this.sessionIdGenerator = sessionIdGenerator;
431         sessionIdGeneratorClass = sessionIdGenerator.getClass();
432     }
433
434
435     /**
436      * @return The descriptive short name of this Manager implementation.
437      */

438     public String getName() {
439         return name;
440     }
441
442     /**
443      * @return The secure random number generator class name.
444      */

445     public String getSecureRandomClass() {
446         return this.secureRandomClass;
447     }
448
449
450     /**
451      * Set the secure random number generator class name.
452      *
453      * @param secureRandomClass The new secure random number generator class
454      *                          name
455      */

456     public void setSecureRandomClass(String secureRandomClass) {
457
458         String oldSecureRandomClass = this.secureRandomClass;
459         this.secureRandomClass = secureRandomClass;
460         support.firePropertyChange("secureRandomClass", oldSecureRandomClass,
461                                    this.secureRandomClass);
462
463     }
464
465
466     /**
467      * @return The secure random number generator algorithm name.
468      */

469     public String getSecureRandomAlgorithm() {
470         return secureRandomAlgorithm;
471     }
472
473
474     /**
475      * Set the secure random number generator algorithm name.
476      *
477      * @param secureRandomAlgorithm The new secure random number generator
478      *                              algorithm name
479      */

480     public void setSecureRandomAlgorithm(String secureRandomAlgorithm) {
481         this.secureRandomAlgorithm = secureRandomAlgorithm;
482     }
483
484
485     /**
486      * @return The secure random number generator provider name.
487      */

488     public String getSecureRandomProvider() {
489         return secureRandomProvider;
490     }
491
492
493     /**
494      * Set the secure random number generator provider name.
495      *
496      * @param secureRandomProvider The new secure random number generator
497      *                             provider name
498      */

499     public void setSecureRandomProvider(String secureRandomProvider) {
500         this.secureRandomProvider = secureRandomProvider;
501     }
502
503
504     @Override
505     public int getRejectedSessions() {
506         return rejectedSessions;
507     }
508
509
510     @Override
511     public long getExpiredSessions() {
512         return expiredSessions.get();
513     }
514
515
516     @Override
517     public void setExpiredSessions(long expiredSessions) {
518         this.expiredSessions.set(expiredSessions);
519     }
520
521     public long getProcessingTime() {
522         return processingTime;
523     }
524
525
526     public void setProcessingTime(long processingTime) {
527         this.processingTime = processingTime;
528     }
529
530     /**
531      * @return The frequency of manager checks.
532      */

533     public int getProcessExpiresFrequency() {
534         return this.processExpiresFrequency;
535     }
536
537     /**
538      * Set the manager checks frequency.
539      *
540      * @param processExpiresFrequency the new manager checks frequency
541      */

542     public void setProcessExpiresFrequency(int processExpiresFrequency) {
543
544         if (processExpiresFrequency <= 0) {
545             return;
546         }
547
548         int oldProcessExpiresFrequency = this.processExpiresFrequency;
549         this.processExpiresFrequency = processExpiresFrequency;
550         support.firePropertyChange("processExpiresFrequency",
551                                    Integer.valueOf(oldProcessExpiresFrequency),
552                                    Integer.valueOf(this.processExpiresFrequency));
553
554     }
555
556
557     /**
558      * Return whether sessions managed by this manager shall persist authentication
559      * information or not.
560      *
561      * @return {@code true}, sessions managed by this manager shall persist
562      *         authentication information; {@code false} otherwise
563      */

564     public boolean getPersistAuthentication() {
565         return this.persistAuthentication;
566     }
567
568     /**
569      * Set whether sessions managed by this manager shall persist authentication
570      * information or not.
571      *
572      * @param persistAuthentication if {@code true}, sessions managed by this manager
573      *                              shall persist authentication information
574      */

575     public void setPersistAuthentication(boolean persistAuthentication) {
576         this.persistAuthentication = persistAuthentication;
577     }
578
579
580     // --------------------------------------------------------- Public Methods
581
582     /**
583      * {@inheritDoc}
584      * <p>
585      * Direct call to {@link #processExpires()}
586      */

587     @Override
588     public void backgroundProcess() {
589         count = (count + 1) % processExpiresFrequency;
590         if (count == 0)
591             processExpires();
592     }
593
594     /**
595      * Invalidate all sessions that have expired.
596      */

597     public void processExpires() {
598
599         long timeNow = System.currentTimeMillis();
600         Session sessions[] = findSessions();
601         int expireHere = 0 ;
602
603         if(log.isDebugEnabled())
604             log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
605         for (int i = 0; i < sessions.length; i++) {
606             if (sessions[i]!=null && !sessions[i].isValid()) {
607                 expireHere++;
608             }
609         }
610         long timeEnd = System.currentTimeMillis();
611         if(log.isDebugEnabled())
612              log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
613         processingTime += ( timeEnd - timeNow );
614
615     }
616
617
618     @Override
619     protected void initInternal() throws LifecycleException {
620         super.initInternal();
621
622         if (context == null) {
623             throw new LifecycleException(sm.getString("managerBase.contextNull"));
624         }
625     }
626
627
628     @Override
629     protected void startInternal() throws LifecycleException {
630
631         // Ensure caches for timing stats are the right size by filling with
632         // nulls.
633         while (sessionCreationTiming.size() < TIMING_STATS_CACHE_SIZE) {
634             sessionCreationTiming.add(null);
635         }
636         while (sessionExpirationTiming.size() < TIMING_STATS_CACHE_SIZE) {
637             sessionExpirationTiming.add(null);
638         }
639
640         /* Create sessionIdGenerator if not explicitly configured */
641         SessionIdGenerator sessionIdGenerator = getSessionIdGenerator();
642         if (sessionIdGenerator == null) {
643             sessionIdGenerator = new StandardSessionIdGenerator();
644             setSessionIdGenerator(sessionIdGenerator);
645         }
646
647         sessionIdGenerator.setJvmRoute(getJvmRoute());
648         if (sessionIdGenerator instanceof SessionIdGeneratorBase) {
649             SessionIdGeneratorBase sig = (SessionIdGeneratorBase)sessionIdGenerator;
650             sig.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
651             sig.setSecureRandomClass(getSecureRandomClass());
652             sig.setSecureRandomProvider(getSecureRandomProvider());
653         }
654
655         if (sessionIdGenerator instanceof Lifecycle) {
656             ((Lifecycle) sessionIdGenerator).start();
657         } else {
658             // Force initialization of the random number generator
659             if (log.isDebugEnabled())
660                 log.debug("Force random number initialization starting");
661             sessionIdGenerator.generateSessionId();
662             if (log.isDebugEnabled())
663                 log.debug("Force random number initialization completed");
664         }
665     }
666
667
668     @Override
669     protected void stopInternal() throws LifecycleException {
670         if (sessionIdGenerator instanceof Lifecycle) {
671             ((Lifecycle) sessionIdGenerator).stop();
672         }
673     }
674
675
676     @Override
677     public void add(Session session) {
678         sessions.put(session.getIdInternal(), session);
679         int size = getActiveSessions();
680         if( size > maxActive ) {
681             synchronized(maxActiveUpdateLock) {
682                 if( size > maxActive ) {
683                     maxActive = size;
684                 }
685             }
686         }
687     }
688
689
690     @Override
691     public void addPropertyChangeListener(PropertyChangeListener listener) {
692         support.addPropertyChangeListener(listener);
693     }
694
695
696     @Override
697     public Session createSession(String sessionId) {
698
699         if ((maxActiveSessions >= 0) &&
700                 (getActiveSessions() >= maxActiveSessions)) {
701             rejectedSessions++;
702             throw new TooManyActiveSessionsException(
703                     sm.getString("managerBase.createSession.ise"),
704                     maxActiveSessions);
705         }
706
707         // Recycle or create a Session instance
708         Session session = createEmptySession();
709
710         // Initialize the properties of the new session and return it
711         session.setNew(true);
712         session.setValid(true);
713         session.setCreationTime(System.currentTimeMillis());
714         session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
715         String id = sessionId;
716         if (id == null) {
717             id = generateSessionId();
718         }
719         session.setId(id);
720         sessionCounter++;
721
722         SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
723         synchronized (sessionCreationTiming) {
724             sessionCreationTiming.add(timing);
725             sessionCreationTiming.poll();
726         }
727         return session;
728     }
729
730
731     @Override
732     public Session createEmptySession() {
733         return getNewSession();
734     }
735
736
737     @Override
738     public Session findSession(String id) throws IOException {
739         if (id == null) {
740             return null;
741         }
742         return sessions.get(id);
743     }
744
745
746     @Override
747     public Session[] findSessions() {
748         return sessions.values().toArray(new Session[0]);
749     }
750
751
752     @Override
753     public void remove(Session session) {
754         remove(session, false);
755     }
756
757
758     @Override
759     public void remove(Session session, boolean update) {
760         // If the session has expired - as opposed to just being removed from
761         // the manager because it is being persisted - update the expired stats
762         if (update) {
763             long timeNow = System.currentTimeMillis();
764             int timeAlive =
765                 (int) (timeNow - session.getCreationTimeInternal())/1000;
766             updateSessionMaxAliveTime(timeAlive);
767             expiredSessions.incrementAndGet();
768             SessionTiming timing = new SessionTiming(timeNow, timeAlive);
769             synchronized (sessionExpirationTiming) {
770                 sessionExpirationTiming.add(timing);
771                 sessionExpirationTiming.poll();
772             }
773         }
774
775         if (session.getIdInternal() != null) {
776             sessions.remove(session.getIdInternal());
777         }
778     }
779
780
781     @Override
782     public void removePropertyChangeListener(PropertyChangeListener listener) {
783         support.removePropertyChangeListener(listener);
784     }
785
786
787     @Override
788     public void changeSessionId(Session session) {
789         rotateSessionId(session);
790     }
791
792
793     @Override
794     public String rotateSessionId(Session session) {
795         String newId = generateSessionId();
796         changeSessionId(session, newId, truetrue);
797         return newId;
798     }
799
800
801     @Override
802     public void changeSessionId(Session session, String newId) {
803         changeSessionId(session, newId, truetrue);
804     }
805
806
807     protected void changeSessionId(Session session, String newId,
808             boolean notifySessionListeners, boolean notifyContainerListeners) {
809         String oldId = session.getIdInternal();
810         session.setId(newId, false);
811         session.tellChangedSessionId(newId, oldId,
812                 notifySessionListeners, notifyContainerListeners);
813     }
814
815
816     /**
817      * {@inheritDoc}
818      * <p>
819      * This implementation excludes session attributes from distribution if the:
820      * <ul>
821      * <li>attribute name matches {@link #getSessionAttributeNameFilter()}</li>
822      * </ul>
823      */

824     @Override
825     public boolean willAttributeDistribute(String name, Object value) {
826         Pattern sessionAttributeNamePattern = getSessionAttributeNamePattern();
827         if (sessionAttributeNamePattern != null) {
828             if (!sessionAttributeNamePattern.matcher(name).matches()) {
829                 if (getWarnOnSessionAttributeFilterFailure() || log.isDebugEnabled()) {
830                     String msg = sm.getString("managerBase.sessionAttributeNameFilter",
831                             name, sessionAttributeNamePattern);
832                     if (getWarnOnSessionAttributeFilterFailure()) {
833                         log.warn(msg);
834                     } else {
835                         log.debug(msg);
836                     }
837                 }
838                 return false;
839             }
840         }
841
842         Pattern sessionAttributeValueClassNamePattern = getSessionAttributeValueClassNamePattern();
843         if (value != null && sessionAttributeValueClassNamePattern != null) {
844             if (!sessionAttributeValueClassNamePattern.matcher(
845                     value.getClass().getName()).matches()) {
846                 if (getWarnOnSessionAttributeFilterFailure() || log.isDebugEnabled()) {
847                     String msg = sm.getString("managerBase.sessionAttributeValueClassNameFilter",
848                             name, value.getClass().getName(), sessionAttributeValueClassNamePattern);
849                     if (getWarnOnSessionAttributeFilterFailure()) {
850                         log.warn(msg);
851                     } else {
852                         log.debug(msg);
853                     }
854                 }
855                 return false;
856             }
857         }
858
859         return true;
860     }
861
862
863     // ------------------------------------------------------ Protected Methods
864
865
866     /**
867      * Get new session class to be used in the doLoad() method.
868      * @return a new session for use with this manager
869      */

870     protected StandardSession getNewSession() {
871         return new StandardSession(this);
872     }
873
874
875     /**
876      * Generate and return a new session identifier.
877      * @return a new session id
878      */

879     protected String generateSessionId() {
880
881         String result = null;
882
883         do {
884             if (result != null) {
885                 // Not thread-safe but if one of multiple increments is lost
886                 // that is not a big deal since the fact that there was any
887                 // duplicate is a much bigger issue.
888                 duplicates++;
889             }
890
891             result = sessionIdGenerator.generateSessionId();
892
893         } while (sessions.containsKey(result));
894
895         return result;
896     }
897
898
899     // ------------------------------------------------------ Protected Methods
900
901
902     /**
903      * Retrieve the enclosing Engine for this Manager.
904      *
905      * @return an Engine object (or null).
906      */

907     public Engine getEngine() {
908         Engine e = null;
909         for (Container c = getContext(); e == null && c != null ; c = c.getParent()) {
910             if (c instanceof Engine) {
911                 e = (Engine)c;
912             }
913         }
914         return e;
915     }
916
917
918     /**
919      * Retrieve the JvmRoute for the enclosing Engine.
920      * @return the JvmRoute or null.
921      */

922     public String getJvmRoute() {
923         Engine e = getEngine();
924         return e == null ? null : e.getJvmRoute();
925     }
926
927
928     // -------------------------------------------------------- Package Methods
929
930
931     @Override
932     public void setSessionCounter(long sessionCounter) {
933         this.sessionCounter = sessionCounter;
934     }
935
936
937     @Override
938     public long getSessionCounter() {
939         return sessionCounter;
940     }
941
942
943     /**
944      * Number of duplicated session IDs generated by the random source.
945      * Anything bigger than 0 means problems.
946      *
947      * @return The count of duplicates
948      */

949     public int getDuplicates() {
950         return duplicates;
951     }
952
953
954     public void setDuplicates(int duplicates) {
955         this.duplicates = duplicates;
956     }
957
958
959     @Override
960     public int getActiveSessions() {
961         return sessions.size();
962     }
963
964
965     @Override
966     public int getMaxActive() {
967         return maxActive;
968     }
969
970
971     @Override
972     public void setMaxActive(int maxActive) {
973         synchronized (maxActiveUpdateLock) {
974             this.maxActive = maxActive;
975         }
976     }
977
978
979     /**
980      * @return The maximum number of active Sessions allowed, or -1 for no
981      *         limit.
982      */

983     public int getMaxActiveSessions() {
984         return this.maxActiveSessions;
985     }
986
987
988     /**
989      * Set the maximum number of active Sessions allowed, or -1 for
990      * no limit.
991      *
992      * @param max The new maximum number of sessions
993      */

994     public void setMaxActiveSessions(int max) {
995
996         int oldMaxActiveSessions = this.maxActiveSessions;
997         this.maxActiveSessions = max;
998         support.firePropertyChange("maxActiveSessions",
999                                    Integer.valueOf(oldMaxActiveSessions),
1000                                    Integer.valueOf(this.maxActiveSessions));
1001
1002     }
1003
1004
1005     @Override
1006     public int getSessionMaxAliveTime() {
1007         return sessionMaxAliveTime;
1008     }
1009
1010
1011     @Override
1012     public void setSessionMaxAliveTime(int sessionMaxAliveTime) {
1013         synchronized (sessionMaxAliveTimeUpdateLock) {
1014             this.sessionMaxAliveTime = sessionMaxAliveTime;
1015         }
1016     }
1017
1018
1019     /**
1020      * Updates the sessionMaxAliveTime attribute if the candidate value is
1021      * larger than the current value.
1022      *
1023      * @param sessionAliveTime  The candidate value (in seconds) for the new
1024      *                          sessionMaxAliveTime value.
1025      */

1026     public void updateSessionMaxAliveTime(int sessionAliveTime) {
1027         if (sessionAliveTime > this.sessionMaxAliveTime) {
1028             synchronized (sessionMaxAliveTimeUpdateLock) {
1029                 if (sessionAliveTime > this.sessionMaxAliveTime) {
1030                     this.sessionMaxAliveTime = sessionAliveTime;
1031                 }
1032             }
1033         }
1034     }
1035
1036     /**
1037      * {@inheritDoc}
1038      * <p>
1039      * Based on the last 100 sessions to expire. If less than 100 sessions have
1040      * expired then all available data is used.
1041      */

1042     @Override
1043     public int getSessionAverageAliveTime() {
1044         // Copy current stats
1045         List<SessionTiming> copy = new ArrayList<>();
1046         synchronized (sessionExpirationTiming) {
1047             copy.addAll(sessionExpirationTiming);
1048         }
1049
1050         // Init
1051         int counter = 0;
1052         int result = 0;
1053
1054         // Calculate average
1055         for (SessionTiming timing : copy) {
1056             if (timing != null) {
1057                 int timeAlive = timing.getDuration();
1058                 counter++;
1059                 // Very careful not to overflow - probably not necessary
1060                 result =
1061                     (result * ((counter - 1)/counter)) + (timeAlive/counter);
1062             }
1063         }
1064         return result;
1065     }
1066
1067
1068     /**
1069      * {@inheritDoc}<p>
1070      * Based on the creation time of the previous 100 sessions created. If less
1071      * than 100 sessions have been created then all available data is used.
1072      */

1073     @Override
1074     public int getSessionCreateRate() {
1075         // Copy current stats
1076         List<SessionTiming> copy = new ArrayList<>();
1077         synchronized (sessionCreationTiming) {
1078             copy.addAll(sessionCreationTiming);
1079         }
1080
1081         return calculateRate(copy);
1082     }
1083
1084
1085     /**
1086      * {@inheritDoc}
1087      * <p>
1088      * Based on the expiry time of the previous 100 sessions expired. If less
1089      * than 100 sessions have expired then all available data is used.
1090      *
1091      * @return  The current rate (in sessions per minute) of session expiration
1092      */

1093     @Override
1094     public int getSessionExpireRate() {
1095         // Copy current stats
1096         List<SessionTiming> copy = new ArrayList<>();
1097         synchronized (sessionExpirationTiming) {
1098             copy.addAll(sessionExpirationTiming);
1099         }
1100
1101         return calculateRate(copy);
1102     }
1103
1104
1105     private static int calculateRate(List<SessionTiming> sessionTiming) {
1106         // Init
1107         long now = System.currentTimeMillis();
1108         long oldest = now;
1109         int counter = 0;
1110         int result = 0;
1111
1112         // Calculate rate
1113         for (SessionTiming timing : sessionTiming) {
1114             if (timing != null) {
1115                 counter++;
1116                 if (timing.getTimestamp() < oldest) {
1117                     oldest = timing.getTimestamp();
1118                 }
1119             }
1120         }
1121         if (counter > 0) {
1122             if (oldest < now) {
1123                 result = (1000*60*counter)/(int) (now - oldest);
1124             } else {
1125                 // Better than reporting zero
1126                 result = Integer.MAX_VALUE;
1127             }
1128         }
1129         return result;
1130     }
1131
1132
1133     /**
1134      * For debugging.
1135      *
1136      * @return A space separated list of all session IDs currently active
1137      */

1138     public String listSessionIds() {
1139         StringBuilder sb = new StringBuilder();
1140         for (String s : sessions.keySet()) {
1141             sb.append(s).append(" ");
1142         }
1143         return sb.toString();
1144     }
1145
1146
1147     /**
1148      * For debugging.
1149      *
1150      * @param sessionId The ID for the session of interest
1151      * @param key       The key for the attribute to obtain
1152      *
1153      * @return The attribute value for the specified session, if found, null
1154      *         otherwise
1155      */

1156     public String getSessionAttribute( String sessionId, String key ) {
1157         Session s = sessions.get(sessionId);
1158         if (s == null) {
1159             if (log.isInfoEnabled()) {
1160                 log.info(sm.getString("managerBase.sessionNotFound", sessionId));
1161             }
1162             return null;
1163         }
1164         Object o=s.getSession().getAttribute(key);
1165         if( o==null ) return null;
1166         return o.toString();
1167     }
1168
1169
1170     /**
1171      * Returns information about the session with the given session id.
1172      *
1173      * <p>The session information is organized as a HashMap, mapping
1174      * session attribute names to the String representation of their values.
1175      *
1176      * @param sessionId Session id
1177      *
1178      * @return HashMap mapping session attribute names to the String
1179      * representation of their values, or null if no session with the
1180      * specified id exists, or if the session does not have any attributes
1181      */

1182     public HashMap<String, String> getSession(String sessionId) {
1183         Session s = sessions.get(sessionId);
1184         if (s == null) {
1185             if (log.isInfoEnabled()) {
1186                 log.info(sm.getString("managerBase.sessionNotFound", sessionId));
1187             }
1188             return null;
1189         }
1190
1191         Enumeration<String> ee = s.getSession().getAttributeNames();
1192         if (ee == null || !ee.hasMoreElements()) {
1193             return null;
1194         }
1195
1196         HashMap<String, String> map = new HashMap<>();
1197         while (ee.hasMoreElements()) {
1198             String attrName = ee.nextElement();
1199             map.put(attrName, getSessionAttribute(sessionId, attrName));
1200         }
1201
1202         return map;
1203     }
1204
1205
1206     public void expireSession( String sessionId ) {
1207         Session s = sessions.get(sessionId);
1208         if (s == null) {
1209             if (log.isInfoEnabled()) {
1210                 log.info(sm.getString("managerBase.sessionNotFound", sessionId));
1211             }
1212             return;
1213         }
1214         s.expire();
1215     }
1216
1217     public long getThisAccessedTimestamp( String sessionId ) {
1218         Session s = sessions.get(sessionId);
1219         if (s == null) {
1220             if (log.isInfoEnabled()) {
1221                 log.info(sm.getString("managerBase.sessionNotFound", sessionId));
1222             }
1223             return -1;
1224         }
1225         return s.getThisAccessedTime();
1226     }
1227
1228     public String getThisAccessedTime( String sessionId ) {
1229         Session s = sessions.get(sessionId);
1230         if (s == null) {
1231             if (log.isInfoEnabled()) {
1232                 log.info(sm.getString("managerBase.sessionNotFound", sessionId));
1233             }
1234             return "";
1235         }
1236         return new Date(s.getThisAccessedTime()).toString();
1237     }
1238
1239     public long getLastAccessedTimestamp( String sessionId ) {
1240         Session s = sessions.get(sessionId);
1241         if (s == null) {
1242             if (log.isInfoEnabled()) {
1243                 log.info(sm.getString("managerBase.sessionNotFound", sessionId));
1244             }
1245             return -1;
1246         }
1247         return s.getLastAccessedTime();
1248     }
1249
1250     public String getLastAccessedTime( String sessionId ) {
1251         Session s = sessions.get(sessionId);
1252         if (s == null) {
1253             if (log.isInfoEnabled()) {
1254                 log.info(sm.getString("managerBase.sessionNotFound", sessionId));
1255             }
1256             return "";
1257         }
1258         return new Date(s.getLastAccessedTime()).toString();
1259     }
1260
1261     public String getCreationTime( String sessionId ) {
1262         Session s = sessions.get(sessionId);
1263         if (s == null) {
1264             if (log.isInfoEnabled()) {
1265                 log.info(sm.getString("managerBase.sessionNotFound", sessionId));
1266             }
1267             return "";
1268         }
1269         return new Date(s.getCreationTime()).toString();
1270     }
1271
1272     public long getCreationTimestamp( String sessionId ) {
1273         Session s = sessions.get(sessionId);
1274         if (s == null) {
1275             if (log.isInfoEnabled()) {
1276                 log.info(sm.getString("managerBase.sessionNotFound", sessionId));
1277             }
1278             return -1;
1279         }
1280         return s.getCreationTime();
1281     }
1282
1283
1284     @Override
1285     public String toString() {
1286         return ToStringUtil.toString(this, context);
1287     }
1288
1289
1290     // -------------------- JMX and Registration  --------------------
1291     @Override
1292     public String getObjectNameKeyProperties() {
1293
1294         StringBuilder name = new StringBuilder("type=Manager");
1295
1296         name.append(",host=");
1297         name.append(context.getParent().getName());
1298
1299         name.append(",context=");
1300         String contextName = context.getName();
1301         if (!contextName.startsWith("/")) {
1302             name.append('/');
1303         }
1304         name.append(contextName);
1305
1306         return name.toString();
1307     }
1308
1309     @Override
1310     public String getDomainInternal() {
1311         return context.getDomain();
1312     }
1313
1314
1315     // ----------------------------------------------------------- Inner classes
1316
1317     protected static final class SessionTiming {
1318         private final long timestamp;
1319         private final int duration;
1320
1321         public SessionTiming(long timestamp, int duration) {
1322             this.timestamp = timestamp;
1323             this.duration = duration;
1324         }
1325
1326         /**
1327          * @return Time stamp associated with this piece of timing information
1328          *         in milliseconds.
1329          */

1330         public long getTimestamp() {
1331             return timestamp;
1332         }
1333
1334         /**
1335          * @return Duration associated with this piece of timing information in
1336          *         seconds.
1337          */

1338         public int getDuration() {
1339             return duration;
1340         }
1341     }
1342 }
1343