1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.session;
20
21
22 import java.beans.PropertyChangeListener;
23 import java.beans.PropertyChangeSupport;
24 import java.io.DataInputStream;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.IOException;
28 import java.lang.reflect.Method;
29 import java.security.AccessController;
30 import java.security.MessageDigest;
31 import java.security.NoSuchAlgorithmException;
32 import java.security.PrivilegedAction;
33 import java.util.Date;
34 import java.util.Enumeration;
35 import java.util.HashMap;
36 import java.util.Iterator;
37 import java.util.Map;
38 import java.util.Random;
39 import java.util.concurrent.ConcurrentHashMap;
40
41 import javax.management.MBeanRegistration;
42 import javax.management.MBeanServer;
43 import javax.management.ObjectName;
44
45 import org.apache.catalina.Container;
46 import org.apache.catalina.Engine;
47 import org.apache.catalina.Globals;
48 import org.apache.catalina.Manager;
49 import org.apache.catalina.Session;
50 import org.apache.catalina.core.StandardContext;
51 import org.apache.catalina.core.StandardHost;
52 import org.apache.catalina.util.StringManager;
53 import org.apache.juli.logging.Log;
54 import org.apache.juli.logging.LogFactory;
55 import org.apache.tomcat.util.modeler.Registry;
56
57
58 /**
59 * Minimal implementation of the <b>Manager</b> interface that supports
60 * no session persistence or distributable capabilities. This class may
61 * be subclassed to create more sophisticated Manager implementations.
62 *
63 * @author Craig R. McClanahan
64 * @version $Revision: 573964 $ $Date: 2007-09-09 11:04:27 +0200 (dim., 09 sept. 2007) $
65 */
66
67 public abstract class ManagerBase implements Manager, MBeanRegistration {
68 protected Log log = LogFactory.getLog(ManagerBase.class);
69
70 // ----------------------------------------------------- Instance Variables
71
72 protected DataInputStream randomIS=null;
73 protected String devRandomSource="/dev/urandom";
74
75 /**
76 * The default message digest algorithm to use if we cannot use
77 * the requested one.
78 */
79 protected static final String DEFAULT_ALGORITHM = "MD5";
80
81
82 /**
83 * The message digest algorithm to be used when generating session
84 * identifiers. This must be an algorithm supported by the
85 * <code>java.security.MessageDigest</code> class on your platform.
86 */
87 protected String algorithm = DEFAULT_ALGORITHM;
88
89
90 /**
91 * The Container with which this Manager is associated.
92 */
93 protected Container container;
94
95
96 /**
97 * Return the MessageDigest implementation to be used when
98 * creating session identifiers.
99 */
100 protected MessageDigest digest = null;
101
102
103 /**
104 * The distributable flag for Sessions created by this Manager. If this
105 * flag is set to <code>true</code>, any user attributes added to a
106 * session controlled by this Manager must be Serializable.
107 */
108 protected boolean distributable;
109
110
111 /**
112 * A String initialization parameter used to increase the entropy of
113 * the initialization of our random number generator.
114 */
115 protected String entropy = null;
116
117
118 /**
119 * The descriptive information string for this implementation.
120 */
121 private static final String info = "ManagerBase/1.0";
122
123
124 /**
125 * The default maximum inactive interval for Sessions created by
126 * this Manager.
127 */
128 protected int maxInactiveInterval = 60;
129
130
131 /**
132 * The session id length of Sessions created by this Manager.
133 */
134 protected int sessionIdLength = 16;
135
136
137 /**
138 * The descriptive name of this Manager implementation (for logging).
139 */
140 protected static String name = "ManagerBase";
141
142
143 /**
144 * A random number generator to use when generating session identifiers.
145 */
146 protected Random random = null;
147
148
149 /**
150 * The Java class name of the random number generator class to be used
151 * when generating session identifiers.
152 */
153 protected String randomClass = "java.security.SecureRandom";
154
155
156 /**
157 * The longest time (in seconds) that an expired session had been alive.
158 */
159 protected int sessionMaxAliveTime;
160
161
162 /**
163 * Average time (in seconds) that expired sessions had been alive.
164 */
165 protected int sessionAverageAliveTime;
166
167
168 /**
169 * Number of sessions that have expired.
170 */
171 protected int expiredSessions = 0;
172
173
174 /**
175 * The set of currently active Sessions for this Manager, keyed by
176 * session identifier.
177 */
178 protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
179
180 // Number of sessions created by this manager
181 protected int sessionCounter=0;
182
183 protected int maxActive=0;
184
185 // number of duplicated session ids - anything >0 means we have problems
186 protected int duplicates=0;
187
188 protected boolean initialized=false;
189
190 /**
191 * Processing time during session expiration.
192 */
193 protected long processingTime = 0;
194
195 /**
196 * Iteration count for background processing.
197 */
198 private int count = 0;
199
200
201 /**
202 * Frequency of the session expiration, and related manager operations.
203 * Manager operations will be done once for the specified amount of
204 * backgrondProcess calls (ie, the lower the amount, the most often the
205 * checks will occur).
206 */
207 protected int processExpiresFrequency = 6;
208
209 /**
210 * The string manager for this package.
211 */
212 protected static StringManager sm =
213 StringManager.getManager(Constants.Package);
214
215 /**
216 * The property change support for this component.
217 */
218 protected PropertyChangeSupport support = new PropertyChangeSupport(this);
219
220
221 // ------------------------------------------------------------- Security classes
222
223
224 private class PrivilegedSetRandomFile implements PrivilegedAction{
225
226 public Object run(){
227 try {
228 File f=new File( devRandomSource );
229 if( ! f.exists() ) return null;
230 randomIS= new DataInputStream( new FileInputStream(f));
231 randomIS.readLong();
232 if( log.isDebugEnabled() )
233 log.debug( "Opening " + devRandomSource );
234 return randomIS;
235 } catch (IOException ex){
236 return null;
237 }
238 }
239 }
240
241
242 // ------------------------------------------------------------- Properties
243
244 /**
245 * Return the message digest algorithm for this Manager.
246 */
247 public String getAlgorithm() {
248
249 return (this.algorithm);
250
251 }
252
253
254 /**
255 * Set the message digest algorithm for this Manager.
256 *
257 * @param algorithm The new message digest algorithm
258 */
259 public void setAlgorithm(String algorithm) {
260
261 String oldAlgorithm = this.algorithm;
262 this.algorithm = algorithm;
263 support.firePropertyChange("algorithm", oldAlgorithm, this.algorithm);
264
265 }
266
267
268 /**
269 * Return the Container with which this Manager is associated.
270 */
271 public Container getContainer() {
272
273 return (this.container);
274
275 }
276
277
278 /**
279 * Set the Container with which this Manager is associated.
280 *
281 * @param container The newly associated Container
282 */
283 public void setContainer(Container container) {
284
285 Container oldContainer = this.container;
286 this.container = container;
287 support.firePropertyChange("container", oldContainer, this.container);
288 }
289
290
291 /** Returns the name of the implementation class.
292 */
293 public String getClassName() {
294 return this.getClass().getName();
295 }
296
297
298 /**
299 * Return the MessageDigest object to be used for calculating
300 * session identifiers. If none has been created yet, initialize
301 * one the first time this method is called.
302 */
303 public synchronized MessageDigest getDigest() {
304
305 if (this.digest == null) {
306 long t1=System.currentTimeMillis();
307 if (log.isDebugEnabled())
308 log.debug(sm.getString("managerBase.getting", algorithm));
309 try {
310 this.digest = MessageDigest.getInstance(algorithm);
311 } catch (NoSuchAlgorithmException e) {
312 log.error(sm.getString("managerBase.digest", algorithm), e);
313 try {
314 this.digest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
315 } catch (NoSuchAlgorithmException f) {
316 log.error(sm.getString("managerBase.digest",
317 DEFAULT_ALGORITHM), e);
318 this.digest = null;
319 }
320 }
321 if (log.isDebugEnabled())
322 log.debug(sm.getString("managerBase.gotten"));
323 long t2=System.currentTimeMillis();
324 if( log.isDebugEnabled() )
325 log.debug("getDigest() " + (t2-t1));
326 }
327
328 return (this.digest);
329
330 }
331
332
333 /**
334 * Return the distributable flag for the sessions supported by
335 * this Manager.
336 */
337 public boolean getDistributable() {
338
339 return (this.distributable);
340
341 }
342
343
344 /**
345 * Set the distributable flag for the sessions supported by this
346 * Manager. If this flag is set, all user data objects added to
347 * sessions associated with this manager must implement Serializable.
348 *
349 * @param distributable The new distributable flag
350 */
351 public void setDistributable(boolean distributable) {
352
353 boolean oldDistributable = this.distributable;
354 this.distributable = distributable;
355 support.firePropertyChange("distributable",
356 new Boolean(oldDistributable),
357 new Boolean(this.distributable));
358
359 }
360
361
362 /**
363 * Return the entropy increaser value, or compute a semi-useful value
364 * if this String has not yet been set.
365 */
366 public String getEntropy() {
367
368 // Calculate a semi-useful value if this has not been set
369 if (this.entropy == null) {
370 // Use APR to get a crypto secure entropy value
371 byte[] result = new byte[32];
372 boolean apr = false;
373 try {
374 String methodName = "random";
375 Class paramTypes[] = new Class[2];
376 paramTypes[0] = result.getClass();
377 paramTypes[1] = int.class;
378 Object paramValues[] = new Object[2];
379 paramValues[0] = result;
380 paramValues[1] = new Integer(32);
381 Method method = Class.forName("org.apache.tomcat.jni.OS")
382 .getMethod(methodName, paramTypes);
383 method.invoke(null, paramValues);
384 apr = true;
385 } catch (Throwable t) {
386 // Ignore
387 }
388 if (apr) {
389 setEntropy(new String(result));
390 } else {
391 setEntropy(this.toString());
392 }
393 }
394
395 return (this.entropy);
396
397 }
398
399
400 /**
401 * Set the entropy increaser value.
402 *
403 * @param entropy The new entropy increaser value
404 */
405 public void setEntropy(String entropy) {
406
407 String oldEntropy = entropy;
408 this.entropy = entropy;
409 support.firePropertyChange("entropy", oldEntropy, this.entropy);
410
411 }
412
413
414 /**
415 * Return descriptive information about this Manager implementation and
416 * the corresponding version number, in the format
417 * <code><description>/<version></code>.
418 */
419 public String getInfo() {
420
421 return (info);
422
423 }
424
425
426 /**
427 * Return the default maximum inactive interval (in seconds)
428 * for Sessions created by this Manager.
429 */
430 public int getMaxInactiveInterval() {
431
432 return (this.maxInactiveInterval);
433
434 }
435
436
437 /**
438 * Set the default maximum inactive interval (in seconds)
439 * for Sessions created by this Manager.
440 *
441 * @param interval The new default value
442 */
443 public void setMaxInactiveInterval(int interval) {
444
445 int oldMaxInactiveInterval = this.maxInactiveInterval;
446 this.maxInactiveInterval = interval;
447 support.firePropertyChange("maxInactiveInterval",
448 new Integer(oldMaxInactiveInterval),
449 new Integer(this.maxInactiveInterval));
450
451 }
452
453
454 /**
455 * Gets the session id length (in bytes) of Sessions created by
456 * this Manager.
457 *
458 * @return The session id length
459 */
460 public int getSessionIdLength() {
461
462 return (this.sessionIdLength);
463
464 }
465
466
467 /**
468 * Sets the session id length (in bytes) for Sessions created by this
469 * Manager.
470 *
471 * @param idLength The session id length
472 */
473 public void setSessionIdLength(int idLength) {
474
475 int oldSessionIdLength = this.sessionIdLength;
476 this.sessionIdLength = idLength;
477 support.firePropertyChange("sessionIdLength",
478 new Integer(oldSessionIdLength),
479 new Integer(this.sessionIdLength));
480
481 }
482
483
484 /**
485 * Return the descriptive short name of this Manager implementation.
486 */
487 public String getName() {
488
489 return (name);
490
491 }
492
493 /**
494 * Use /dev/random-type special device. This is new code, but may reduce
495 * the big delay in generating the random.
496 *
497 * You must specify a path to a random generator file. Use /dev/urandom
498 * for linux ( or similar ) systems. Use /dev/random for maximum security
499 * ( it may block if not enough "random" exist ). You can also use
500 * a pipe that generates random.
501 *
502 * The code will check if the file exists, and default to java Random
503 * if not found. There is a significant performance difference, very
504 * visible on the first call to getSession ( like in the first JSP )
505 * - so use it if available.
506 */
507 public void setRandomFile( String s ) {
508 // as a hack, you can use a static file - and genarate the same
509 // session ids ( good for strange debugging )
510 if (Globals.IS_SECURITY_ENABLED){
511 randomIS = (DataInputStream)AccessController.doPrivileged(new PrivilegedSetRandomFile());
512 } else {
513 try{
514 devRandomSource=s;
515 File f=new File( devRandomSource );
516 if( ! f.exists() ) return;
517 randomIS= new DataInputStream( new FileInputStream(f));
518 randomIS.readLong();
519 if( log.isDebugEnabled() )
520 log.debug( "Opening " + devRandomSource );
521 } catch( IOException ex ) {
522 try {
523 randomIS.close();
524 } catch (Exception e) {
525 log.warn("Failed to close randomIS.");
526 }
527
528 randomIS=null;
529 }
530 }
531 }
532
533 public String getRandomFile() {
534 return devRandomSource;
535 }
536
537
538 /**
539 * Return the random number generator instance we should use for
540 * generating session identifiers. If there is no such generator
541 * currently defined, construct and seed a new one.
542 */
543 public Random getRandom() {
544 if (this.random == null) {
545 // Calculate the new random number generator seed
546 long seed = System.currentTimeMillis();
547 long t1 = seed;
548 char entropy[] = getEntropy().toCharArray();
549 for (int i = 0; i < entropy.length; i++) {
550 long update = ((byte) entropy[i]) << ((i % 8) * 8);
551 seed ^= update;
552 }
553 try {
554 // Construct and seed a new random number generator
555 Class clazz = Class.forName(randomClass);
556 this.random = (Random) clazz.newInstance();
557 this.random.setSeed(seed);
558 } catch (Exception e) {
559 // Fall back to the simple case
560 log.error(sm.getString("managerBase.random", randomClass),
561 e);
562 this.random = new java.util.Random();
563 this.random.setSeed(seed);
564 }
565 if(log.isDebugEnabled()) {
566 long t2=System.currentTimeMillis();
567 if( (t2-t1) > 100 )
568 log.debug(sm.getString("managerBase.seeding", randomClass) + " " + (t2-t1));
569 }
570 }
571
572 return (this.random);
573
574 }
575
576
577 /**
578 * Return the random number generator class name.
579 */
580 public String getRandomClass() {
581
582 return (this.randomClass);
583
584 }
585
586
587 /**
588 * Set the random number generator class name.
589 *
590 * @param randomClass The new random number generator class name
591 */
592 public void setRandomClass(String randomClass) {
593
594 String oldRandomClass = this.randomClass;
595 this.randomClass = randomClass;
596 support.firePropertyChange("randomClass", oldRandomClass,
597 this.randomClass);
598
599 }
600
601
602 /**
603 * Gets the number of sessions that have expired.
604 *
605 * @return Number of sessions that have expired
606 */
607 public int getExpiredSessions() {
608 return expiredSessions;
609 }
610
611
612 /**
613 * Sets the number of sessions that have expired.
614 *
615 * @param expiredSessions Number of sessions that have expired
616 */
617 public void setExpiredSessions(int expiredSessions) {
618 this.expiredSessions = expiredSessions;
619 }
620
621 public long getProcessingTime() {
622 return processingTime;
623 }
624
625
626 public void setProcessingTime(long processingTime) {
627 this.processingTime = processingTime;
628 }
629
630 /**
631 * Return the frequency of manager checks.
632 */
633 public int getProcessExpiresFrequency() {
634
635 return (this.processExpiresFrequency);
636
637 }
638
639 /**
640 * Set the manager checks frequency.
641 *
642 * @param processExpiresFrequency the new manager checks frequency
643 */
644 public void setProcessExpiresFrequency(int processExpiresFrequency) {
645
646 if (processExpiresFrequency <= 0) {
647 return;
648 }
649
650 int oldProcessExpiresFrequency = this.processExpiresFrequency;
651 this.processExpiresFrequency = processExpiresFrequency;
652 support.firePropertyChange("processExpiresFrequency",
653 new Integer(oldProcessExpiresFrequency),
654 new Integer(this.processExpiresFrequency));
655
656 }
657
658 // --------------------------------------------------------- Public Methods
659
660
661 /**
662 * Implements the Manager interface, direct call to processExpires
663 */
664 public void backgroundProcess() {
665 count = (count + 1) % processExpiresFrequency;
666 if (count == 0)
667 processExpires();
668 }
669
670 /**
671 * Invalidate all sessions that have expired.
672 */
673 public void processExpires() {
674
675 long timeNow = System.currentTimeMillis();
676 Session sessions[] = findSessions();
677 int expireHere = 0 ;
678
679 if(log.isDebugEnabled())
680 log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
681 for (int i = 0; i < sessions.length; i++) {
682 if (!sessions[i].isValid()) {
683 expireHere++;
684 }
685 }
686 long timeEnd = System.currentTimeMillis();
687 if(log.isDebugEnabled())
688 log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
689 processingTime += ( timeEnd - timeNow );
690
691 }
692
693 public void destroy() {
694 if( oname != null )
695 Registry.getRegistry(null, null).unregisterComponent(oname);
696 initialized=false;
697 oname = null;
698 }
699
700 public void init() {
701 if( initialized ) return;
702 initialized=true;
703
704 log = LogFactory.getLog(ManagerBase.class);
705
706 if( oname==null ) {
707 try {
708 StandardContext ctx=(StandardContext)this.getContainer();
709 Engine eng=(Engine)ctx.getParent().getParent();
710 domain=ctx.getEngineName();
711 distributable = ctx.getDistributable();
712 StandardHost hst=(StandardHost)ctx.getParent();
713 String path = ctx.getPath();
714 if (path.equals("")) {
715 path = "/";
716 }
717 oname=new ObjectName(domain + ":type=Manager,path="
718 + path + ",host=" + hst.getName());
719 Registry.getRegistry(null, null).registerComponent(this, oname, null );
720 } catch (Exception e) {
721 log.error("Error registering ",e);
722 }
723 }
724
725 // Initialize random number generation
726 getRandomBytes(new byte[16]);
727
728 if(log.isDebugEnabled())
729 log.debug("Registering " + oname );
730
731 }
732
733 /**
734 * Add this Session to the set of active Sessions for this Manager.
735 *
736 * @param session Session to be added
737 */
738 public void add(Session session) {
739
740 sessions.put(session.getIdInternal(), session);
741 int size = sessions.size();
742 if( size > maxActive ) {
743 maxActive = size;
744 }
745 }
746
747
748 /**
749 * Add a property change listener to this component.
750 *
751 * @param listener The listener to add
752 */
753 public void addPropertyChangeListener(PropertyChangeListener listener) {
754
755 support.addPropertyChangeListener(listener);
756
757 }
758
759
760 /**
761 * Construct and return a new session object, based on the default
762 * settings specified by this Manager's properties. The session
763 * id will be assigned by this method, and available via the getId()
764 * method of the returned session. If a new session cannot be created
765 * for any reason, return <code>null</code>.
766 *
767 * @exception IllegalStateException if a new session cannot be
768 * instantiated for any reason
769 * @deprecated
770 */
771 public Session createSession() {
772 return createSession(null);
773 }
774
775
776 /**
777 * Construct and return a new session object, based on the default
778 * settings specified by this Manager's properties. The session
779 * id specified will be used as the session id.
780 * If a new session cannot be created for any reason, return
781 * <code>null</code>.
782 *
783 * @param sessionId The session id which should be used to create the
784 * new session; if <code>null</code>, a new session id will be
785 * generated
786 * @exception IllegalStateException if a new session cannot be
787 * instantiated for any reason
788 */
789 public Session createSession(String sessionId) {
790
791 // Recycle or create a Session instance
792 Session session = createEmptySession();
793
794 // Initialize the properties of the new session and return it
795 session.setNew(true);
796 session.setValid(true);
797 session.setCreationTime(System.currentTimeMillis());
798 session.setMaxInactiveInterval(this.maxInactiveInterval);
799 if (sessionId == null) {
800 sessionId = generateSessionId();
801 // FIXME WHy we need no duplication check?
802 /*
803 synchronized (sessions) {
804 while (sessions.get(sessionId) != null) { // Guarantee
805 // uniqueness
806 duplicates++;
807 sessionId = generateSessionId();
808 }
809 }
810 */
811
812 // FIXME: Code to be used in case route replacement is needed
813 /*
814 } else {
815 String jvmRoute = getJvmRoute();
816 if (getJvmRoute() != null) {
817 String requestJvmRoute = null;
818 int index = sessionId.indexOf(".");
819 if (index > 0) {
820 requestJvmRoute = sessionId
821 .substring(index + 1, sessionId.length());
822 }
823 if (requestJvmRoute != null && !requestJvmRoute.equals(jvmRoute)) {
824 sessionId = sessionId.substring(0, index) + "." + jvmRoute;
825 }
826 }
827 */
828 }
829 session.setId(sessionId);
830 sessionCounter++;
831
832 return (session);
833
834 }
835
836
837 /**
838 * Get a session from the recycled ones or create a new empty one.
839 * The PersistentManager manager does not need to create session data
840 * because it reads it from the Store.
841 */
842 public Session createEmptySession() {
843 return (getNewSession());
844 }
845
846
847 /**
848 * Return the active Session, associated with this Manager, with the
849 * specified session id (if any); otherwise return <code>null</code>.
850 *
851 * @param id The session id for the session to be returned
852 *
853 * @exception IllegalStateException if a new session cannot be
854 * instantiated for any reason
855 * @exception IOException if an input/output error occurs while
856 * processing this request
857 */
858 public Session findSession(String id) throws IOException {
859
860 if (id == null)
861 return (null);
862 return (Session) sessions.get(id);
863
864 }
865
866
867 /**
868 * Return the set of active Sessions associated with this Manager.
869 * If this Manager has no active Sessions, a zero-length array is returned.
870 */
871 public Session[] findSessions() {
872
873 return sessions.values().toArray(new Session[0]);
874
875 }
876
877
878 /**
879 * Remove this Session from the active Sessions for this Manager.
880 *
881 * @param session Session to be removed
882 */
883 public void remove(Session session) {
884
885 sessions.remove(session.getIdInternal());
886
887 }
888
889
890 /**
891 * Remove a property change listener from this component.
892 *
893 * @param listener The listener to remove
894 */
895 public void removePropertyChangeListener(PropertyChangeListener listener) {
896
897 support.removePropertyChangeListener(listener);
898
899 }
900
901
902 // ------------------------------------------------------ Protected Methods
903
904
905 /**
906 * Get new session class to be used in the doLoad() method.
907 */
908 protected StandardSession getNewSession() {
909 return new StandardSession(this);
910 }
911
912
913 protected void getRandomBytes(byte bytes[]) {
914 // Generate a byte array containing a session identifier
915 if (devRandomSource != null && randomIS == null) {
916 setRandomFile(devRandomSource);
917 }
918 if (randomIS != null) {
919 try {
920 int len = randomIS.read(bytes);
921 if (len == bytes.length) {
922 return;
923 }
924 if(log.isDebugEnabled())
925 log.debug("Got " + len + " " + bytes.length );
926 } catch (Exception ex) {
927 // Ignore
928 }
929 devRandomSource = null;
930
931 try {
932 randomIS.close();
933 } catch (Exception e) {
934 log.warn("Failed to close randomIS.");
935 }
936
937 randomIS = null;
938 }
939 getRandom().nextBytes(bytes);
940 }
941
942
943 /**
944 * Generate and return a new session identifier.
945 */
946 protected synchronized String generateSessionId() {
947
948 byte random[] = new byte[16];
949 String jvmRoute = getJvmRoute();
950 String result = null;
951
952 // Render the result as a String of hexadecimal digits
953 StringBuffer buffer = new StringBuffer();
954 do {
955 int resultLenBytes = 0;
956 if (result != null) {
957 buffer = new StringBuffer();
958 duplicates++;
959 }
960
961 while (resultLenBytes < this.sessionIdLength) {
962 getRandomBytes(random);
963 random = getDigest().digest(random);
964 for (int j = 0;
965 j < random.length && resultLenBytes < this.sessionIdLength;
966 j++) {
967 byte b1 = (byte) ((random[j] & 0xf0) >> 4);
968 byte b2 = (byte) (random[j] & 0x0f);
969 if (b1 < 10)
970 buffer.append((char) ('0' + b1));
971 else
972 buffer.append((char) ('A' + (b1 - 10)));
973 if (b2 < 10)
974 buffer.append((char) ('0' + b2));
975 else
976 buffer.append((char) ('A' + (b2 - 10)));
977 resultLenBytes++;
978 }
979 }
980 if (jvmRoute != null) {
981 buffer.append('.').append(jvmRoute);
982 }
983 result = buffer.toString();
984 } while (sessions.containsKey(result));
985 return (result);
986
987 }
988
989
990 // ------------------------------------------------------ Protected Methods
991
992
993 /**
994 * Retrieve the enclosing Engine for this Manager.
995 *
996 * @return an Engine object (or null).
997 */
998 public Engine getEngine() {
999 Engine e = null;
1000 for (Container c = getContainer(); e == null && c != null ; c = c.getParent()) {
1001 if (c != null && c instanceof Engine) {
1002 e = (Engine)c;
1003 }
1004 }
1005 return e;
1006 }
1007
1008
1009 /**
1010 * Retrieve the JvmRoute for the enclosing Engine.
1011 * @return the JvmRoute or null.
1012 */
1013 public String getJvmRoute() {
1014 Engine e = getEngine();
1015 return e == null ? null : e.getJvmRoute();
1016 }
1017
1018
1019 // -------------------------------------------------------- Package Methods
1020
1021
1022 public void setSessionCounter(int sessionCounter) {
1023 this.sessionCounter = sessionCounter;
1024 }
1025
1026
1027 /**
1028 * Total sessions created by this manager.
1029 *
1030 * @return sessions created
1031 */
1032 public int getSessionCounter() {
1033 return sessionCounter;
1034 }
1035
1036
1037 /**
1038 * Number of duplicated session IDs generated by the random source.
1039 * Anything bigger than 0 means problems.
1040 *
1041 * @return The count of duplicates
1042 */
1043 public int getDuplicates() {
1044 return duplicates;
1045 }
1046
1047
1048 public void setDuplicates(int duplicates) {
1049 this.duplicates = duplicates;
1050 }
1051
1052
1053 /**
1054 * Returns the number of active sessions
1055 *
1056 * @return number of sessions active
1057 */
1058 public int getActiveSessions() {
1059 return sessions.size();
1060 }
1061
1062
1063 /**
1064 * Max number of concurrent active sessions
1065 *
1066 * @return The highest number of concurrent active sessions
1067 */
1068 public int getMaxActive() {
1069 return maxActive;
1070 }
1071
1072
1073 public void setMaxActive(int maxActive) {
1074 this.maxActive = maxActive;
1075 }
1076
1077
1078 /**
1079 * Gets the longest time (in seconds) that an expired session had been
1080 * alive.
1081 *
1082 * @return Longest time (in seconds) that an expired session had been
1083 * alive.
1084 */
1085 public int getSessionMaxAliveTime() {
1086 return sessionMaxAliveTime;
1087 }
1088
1089
1090 /**
1091 * Sets the longest time (in seconds) that an expired session had been
1092 * alive.
1093 *
1094 * @param sessionMaxAliveTime Longest time (in seconds) that an expired
1095 * session had been alive.
1096 */
1097 public void setSessionMaxAliveTime(int sessionMaxAliveTime) {
1098 this.sessionMaxAliveTime = sessionMaxAliveTime;
1099 }
1100
1101
1102 /**
1103 * Gets the average time (in seconds) that expired sessions had been
1104 * alive.
1105 *
1106 * @return Average time (in seconds) that expired sessions had been
1107 * alive.
1108 */
1109 public int getSessionAverageAliveTime() {
1110 return sessionAverageAliveTime;
1111 }
1112
1113
1114 /**
1115 * Sets the average time (in seconds) that expired sessions had been
1116 * alive.
1117 *
1118 * @param sessionAverageAliveTime Average time (in seconds) that expired
1119 * sessions had been alive.
1120 */
1121 public void setSessionAverageAliveTime(int sessionAverageAliveTime) {
1122 this.sessionAverageAliveTime = sessionAverageAliveTime;
1123 }
1124
1125
1126 /**
1127 * For debugging: return a list of all session ids currently active
1128 *
1129 */
1130 public String listSessionIds() {
1131 StringBuffer sb=new StringBuffer();
1132 Iterator keys = sessions.keySet().iterator();
1133 while (keys.hasNext()) {
1134 sb.append(keys.next()).append(" ");
1135 }
1136 return sb.toString();
1137 }
1138
1139
1140 /**
1141 * For debugging: get a session attribute
1142 *
1143 * @param sessionId
1144 * @param key
1145 * @return The attribute value, if found, null otherwise
1146 */
1147 public String getSessionAttribute( String sessionId, String key ) {
1148 Session s = (Session) sessions.get(sessionId);
1149 if( s==null ) {
1150 if(log.isInfoEnabled())
1151 log.info("Session not found " + sessionId);
1152 return null;
1153 }
1154 Object o=s.getSession().getAttribute(key);
1155 if( o==null ) return null;
1156 return o.toString();
1157 }
1158
1159
1160 /**
1161 * Returns information about the session with the given session id.
1162 *
1163 * <p>The session information is organized as a HashMap, mapping
1164 * session attribute names to the String representation of their values.
1165 *
1166 * @param sessionId Session id
1167 *
1168 * @return HashMap mapping session attribute names to the String
1169 * representation of their values, or null if no session with the
1170 * specified id exists, or if the session does not have any attributes
1171 */
1172 public HashMap getSession(String sessionId) {
1173 Session s = (Session) sessions.get(sessionId);
1174 if (s == null) {
1175 if (log.isInfoEnabled()) {
1176 log.info("Session not found " + sessionId);
1177 }
1178 return null;
1179 }
1180
1181 Enumeration ee = s.getSession().getAttributeNames();
1182 if (ee == null || !ee.hasMoreElements()) {
1183 return null;
1184 }
1185
1186 HashMap map = new HashMap();
1187 while (ee.hasMoreElements()) {
1188 String attrName = (String) ee.nextElement();
1189 map.put(attrName, getSessionAttribute(sessionId, attrName));
1190 }
1191
1192 return map;
1193 }
1194
1195
1196 public void expireSession( String sessionId ) {
1197 Session s=(Session)sessions.get(sessionId);
1198 if( s==null ) {
1199 if(log.isInfoEnabled())
1200 log.info("Session not found " + sessionId);
1201 return;
1202 }
1203 s.expire();
1204 }
1205
1206
1207 public String getLastAccessedTime( String sessionId ) {
1208 Session s=(Session)sessions.get(sessionId);
1209 if( s==null ) {
1210 if(log.isInfoEnabled())
1211 log.info("Session not found " + sessionId);
1212 return "";
1213 }
1214 return new Date(s.getLastAccessedTime()).toString();
1215 }
1216
1217 public String getCreationTime( String sessionId ) {
1218 Session s=(Session)sessions.get(sessionId);
1219 if( s==null ) {
1220 if(log.isInfoEnabled())
1221 log.info("Session not found " + sessionId);
1222 return "";
1223 }
1224 return new Date(s.getCreationTime()).toString();
1225 }
1226
1227 // -------------------- JMX and Registration --------------------
1228 protected String domain;
1229 protected ObjectName oname;
1230 protected MBeanServer mserver;
1231
1232 public ObjectName getObjectName() {
1233 return oname;
1234 }
1235
1236 public String getDomain() {
1237 return domain;
1238 }
1239
1240 public ObjectName preRegister(MBeanServer server,
1241 ObjectName name) throws Exception {
1242 oname=name;
1243 mserver=server;
1244 domain=name.getDomain();
1245 return name;
1246 }
1247
1248 public void postRegister(Boolean registrationDone) {
1249 }
1250
1251 public void preDeregister() throws Exception {
1252 }
1253
1254 public void postDeregister() {
1255 }
1256
1257 }