001: /*
002: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/session/ManagerBase.java,v 1.11 2002/01/14 23:38:03 remm Exp $
003: * $Revision: 1.11 $
004: * $Date: 2002/01/14 23:38:03 $
005: *
006: * ====================================================================
007: *
008: * The Apache Software License, Version 1.1
009: *
010: * Copyright (c) 1999 The Apache Software Foundation. All rights
011: * reserved.
012: *
013: * Redistribution and use in source and binary forms, with or without
014: * modification, are permitted provided that the following conditions
015: * are met:
016: *
017: * 1. Redistributions of source code must retain the above copyright
018: * notice, this list of conditions and the following disclaimer.
019: *
020: * 2. Redistributions in binary form must reproduce the above copyright
021: * notice, this list of conditions and the following disclaimer in
022: * the documentation and/or other materials provided with the
023: * distribution.
024: *
025: * 3. The end-user documentation included with the redistribution, if
026: * any, must include the following acknowlegement:
027: * "This product includes software developed by the
028: * Apache Software Foundation (http://www.apache.org/)."
029: * Alternately, this acknowlegement may appear in the software itself,
030: * if and wherever such third-party acknowlegements normally appear.
031: *
032: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
033: * Foundation" must not be used to endorse or promote products derived
034: * from this software without prior written permission. For written
035: * permission, please contact apache@apache.org.
036: *
037: * 5. Products derived from this software may not be called "Apache"
038: * nor may "Apache" appear in their names without prior written
039: * permission of the Apache Group.
040: *
041: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
042: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
043: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
044: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
045: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
046: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
047: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
048: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
049: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
050: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
051: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
052: * SUCH DAMAGE.
053: * ====================================================================
054: *
055: * This software consists of voluntary contributions made by many
056: * individuals on behalf of the Apache Software Foundation. For more
057: * information on the Apache Software Foundation, please see
058: * <http://www.apache.org/>.
059: *
060: * [Additional notices, if required by prior licensing conditions]
061: *
062: */
063:
064: package org.apache.catalina.session;
065:
066: import java.beans.PropertyChangeListener;
067: import java.beans.PropertyChangeSupport;
068: import java.io.IOException;
069: import java.security.MessageDigest;
070: import java.security.NoSuchAlgorithmException;
071: import java.util.ArrayList;
072: import java.util.HashMap;
073: import java.util.Random;
074: import org.apache.catalina.Container;
075: import org.apache.catalina.Engine;
076: import org.apache.catalina.Logger;
077: import org.apache.catalina.Manager;
078: import org.apache.catalina.Session;
079: import org.apache.catalina.util.StringManager;
080:
081: /**
082: * Minimal implementation of the <b>Manager</b> interface that supports
083: * no session persistence or distributable capabilities. This class may
084: * be subclassed to create more sophisticated Manager implementations.
085: *
086: * @author Craig R. McClanahan
087: * @version $Revision: 1.11 $ $Date: 2002/01/14 23:38:03 $
088: */
089:
090: public abstract class ManagerBase implements Manager {
091:
092: // ----------------------------------------------------- Instance Variables
093:
094: /**
095: * The default message digest algorithm to use if we cannot use
096: * the requested one.
097: */
098: protected static final String DEFAULT_ALGORITHM = "MD5";
099:
100: /**
101: * The number of random bytes to include when generating a
102: * session identifier.
103: */
104: protected static final int SESSION_ID_BYTES = 16;
105:
106: /**
107: * The message digest algorithm to be used when generating session
108: * identifiers. This must be an algorithm supported by the
109: * <code>java.security.MessageDigest</code> class on your platform.
110: */
111: protected String algorithm = DEFAULT_ALGORITHM;
112:
113: /**
114: * The Container with which this Manager is associated.
115: */
116: protected Container container;
117:
118: /**
119: * The debugging detail level for this component.
120: */
121: protected int debug = 0;
122:
123: /**
124: * Return the MessageDigest implementation to be used when
125: * creating session identifiers.
126: */
127: protected MessageDigest digest = null;
128:
129: /**
130: * The distributable flag for Sessions created by this Manager. If this
131: * flag is set to <code>true</code>, any user attributes added to a
132: * session controlled by this Manager must be Serializable.
133: */
134: protected boolean distributable;
135:
136: /**
137: * A String initialization parameter used to increase the entropy of
138: * the initialization of our random number generator.
139: */
140: protected String entropy = null;
141:
142: /**
143: * The descriptive information string for this implementation.
144: */
145: private static final String info = "ManagerBase/1.0";
146:
147: /**
148: * The default maximum inactive interval for Sessions created by
149: * this Manager.
150: */
151: protected int maxInactiveInterval = 60;
152:
153: /**
154: * The descriptive name of this Manager implementation (for logging).
155: */
156: protected static String name = "ManagerBase";
157:
158: /**
159: * A random number generator to use when generating session identifiers.
160: */
161: protected Random random = null;
162:
163: /**
164: * The Java class name of the random number generator class to be used
165: * when generating session identifiers.
166: */
167: protected String randomClass = "java.security.SecureRandom";
168:
169: /**
170: * The set of previously recycled Sessions for this Manager.
171: */
172: protected ArrayList recycled = new ArrayList();
173:
174: /**
175: * The set of currently active Sessions for this Manager, keyed by
176: * session identifier.
177: */
178: protected HashMap sessions = new HashMap();
179:
180: /**
181: * The string manager for this package.
182: */
183: protected static StringManager sm = StringManager
184: .getManager(Constants.Package);
185:
186: /**
187: * The property change support for this component.
188: */
189: protected PropertyChangeSupport support = new PropertyChangeSupport(
190: this );
191:
192: // ------------------------------------------------------------- Properties
193:
194: /**
195: * Return the message digest algorithm for this Manager.
196: */
197: public String getAlgorithm() {
198:
199: return (this .algorithm);
200:
201: }
202:
203: /**
204: * Set the message digest algorithm for this Manager.
205: *
206: * @param algorithm The new message digest algorithm
207: */
208: public void setAlgorithm(String algorithm) {
209:
210: String oldAlgorithm = this .algorithm;
211: this .algorithm = algorithm;
212: support.firePropertyChange("algorithm", oldAlgorithm,
213: this .algorithm);
214:
215: }
216:
217: /**
218: * Return the Container with which this Manager is associated.
219: */
220: public Container getContainer() {
221:
222: return (this .container);
223:
224: }
225:
226: /**
227: * Set the Container with which this Manager is associated.
228: *
229: * @param container The newly associated Container
230: */
231: public void setContainer(Container container) {
232:
233: Container oldContainer = this .container;
234: this .container = container;
235: support.firePropertyChange("container", oldContainer,
236: this .container);
237:
238: }
239:
240: /**
241: * Return the debugging detail level for this component.
242: */
243: public int getDebug() {
244:
245: return (this .debug);
246:
247: }
248:
249: /**
250: * Set the debugging detail level for this component.
251: *
252: * @param debug The new debugging detail level
253: */
254: public void setDebug(int debug) {
255:
256: this .debug = debug;
257:
258: }
259:
260: /**
261: * Return the MessageDigest object to be used for calculating
262: * session identifiers. If none has been created yet, initialize
263: * one the first time this method is called.
264: */
265: public synchronized MessageDigest getDigest() {
266:
267: if (this .digest == null) {
268: if (debug >= 1)
269: log(sm.getString("managerBase.getting", algorithm));
270: try {
271: this .digest = MessageDigest.getInstance(algorithm);
272: } catch (NoSuchAlgorithmException e) {
273: log(sm.getString("managerBase.digest", algorithm), e);
274: try {
275: this .digest = MessageDigest
276: .getInstance(DEFAULT_ALGORITHM);
277: } catch (NoSuchAlgorithmException f) {
278: log(sm.getString("managerBase.digest",
279: DEFAULT_ALGORITHM), e);
280: this .digest = null;
281: }
282: }
283: if (debug >= 1)
284: log(sm.getString("managerBase.gotten"));
285: }
286:
287: return (this .digest);
288:
289: }
290:
291: /**
292: * Return the distributable flag for the sessions supported by
293: * this Manager.
294: */
295: public boolean getDistributable() {
296:
297: return (this .distributable);
298:
299: }
300:
301: /**
302: * Set the distributable flag for the sessions supported by this
303: * Manager. If this flag is set, all user data objects added to
304: * sessions associated with this manager must implement Serializable.
305: *
306: * @param distributable The new distributable flag
307: */
308: public void setDistributable(boolean distributable) {
309:
310: boolean oldDistributable = this .distributable;
311: this .distributable = distributable;
312: support.firePropertyChange("distributable", new Boolean(
313: oldDistributable), new Boolean(this .distributable));
314:
315: }
316:
317: /**
318: * Return the entropy increaser value, or compute a semi-useful value
319: * if this String has not yet been set.
320: */
321: public String getEntropy() {
322:
323: // Calculate a semi-useful value if this has not been set
324: if (this .entropy == null)
325: setEntropy(this .toString());
326:
327: return (this .entropy);
328:
329: }
330:
331: /**
332: * Set the entropy increaser value.
333: *
334: * @param entropy The new entropy increaser value
335: */
336: public void setEntropy(String entropy) {
337:
338: String oldEntropy = entropy;
339: this .entropy = entropy;
340: support.firePropertyChange("entropy", oldEntropy, this .entropy);
341:
342: }
343:
344: /**
345: * Return descriptive information about this Manager implementation and
346: * the corresponding version number, in the format
347: * <code><description>/<version></code>.
348: */
349: public String getInfo() {
350:
351: return (this .info);
352:
353: }
354:
355: /**
356: * Return the default maximum inactive interval (in seconds)
357: * for Sessions created by this Manager.
358: */
359: public int getMaxInactiveInterval() {
360:
361: return (this .maxInactiveInterval);
362:
363: }
364:
365: /**
366: * Set the default maximum inactive interval (in seconds)
367: * for Sessions created by this Manager.
368: *
369: * @param interval The new default value
370: */
371: public void setMaxInactiveInterval(int interval) {
372:
373: int oldMaxInactiveInterval = this .maxInactiveInterval;
374: this .maxInactiveInterval = interval;
375: support.firePropertyChange("maxInactiveInterval", new Integer(
376: oldMaxInactiveInterval), new Integer(
377: this .maxInactiveInterval));
378:
379: }
380:
381: /**
382: * Return the descriptive short name of this Manager implementation.
383: */
384: public String getName() {
385:
386: return (name);
387:
388: }
389:
390: /**
391: * Return the random number generator instance we should use for
392: * generating session identifiers. If there is no such generator
393: * currently defined, construct and seed a new one.
394: */
395: public synchronized Random getRandom() {
396:
397: if (this .random == null) {
398: synchronized (this ) {
399: if (this .random == null) {
400: // Calculate the new random number generator seed
401: log(sm
402: .getString("managerBase.seeding",
403: randomClass));
404: long seed = System.currentTimeMillis();
405: char entropy[] = getEntropy().toCharArray();
406: for (int i = 0; i < entropy.length; i++) {
407: long update = ((byte) entropy[i]) << ((i % 8) * 8);
408: seed ^= update;
409: }
410: try {
411: // Construct and seed a new random number generator
412: Class clazz = Class.forName(randomClass);
413: this .random = (Random) clazz.newInstance();
414: this .random.setSeed(seed);
415: } catch (Exception e) {
416: // Fall back to the simple case
417: log(sm.getString("managerBase.random",
418: randomClass), e);
419: this .random = new java.util.Random();
420: this .random.setSeed(seed);
421: }
422: log(sm.getString("managerBase.complete",
423: randomClass));
424: }
425: }
426: }
427:
428: return (this .random);
429:
430: }
431:
432: /**
433: * Return the random number generator class name.
434: */
435: public String getRandomClass() {
436:
437: return (this .randomClass);
438:
439: }
440:
441: /**
442: * Set the random number generator class name.
443: *
444: * @param randomClass The new random number generator class name
445: */
446: public void setRandomClass(String randomClass) {
447:
448: String oldRandomClass = this .randomClass;
449: this .randomClass = randomClass;
450: support.firePropertyChange("randomClass", oldRandomClass,
451: this .randomClass);
452:
453: }
454:
455: // --------------------------------------------------------- Public Methods
456:
457: /**
458: * Add this Session to the set of active Sessions for this Manager.
459: *
460: * @param session Session to be added
461: */
462: public void add(Session session) {
463:
464: synchronized (sessions) {
465: sessions.put(session.getId(), session);
466: }
467:
468: }
469:
470: /**
471: * Add a property change listener to this component.
472: *
473: * @param listener The listener to add
474: */
475: public void addPropertyChangeListener(
476: PropertyChangeListener listener) {
477:
478: support.addPropertyChangeListener(listener);
479:
480: }
481:
482: /**
483: * Construct and return a new session object, based on the default
484: * settings specified by this Manager's properties. The session
485: * id will be assigned by this method, and available via the getId()
486: * method of the returned session. If a new session cannot be created
487: * for any reason, return <code>null</code>.
488: *
489: * @exception IllegalStateException if a new session cannot be
490: * instantiated for any reason
491: */
492: public Session createSession() {
493:
494: // Recycle or create a Session instance
495: Session session = null;
496: synchronized (recycled) {
497: int size = recycled.size();
498: if (size > 0) {
499: session = (Session) recycled.get(size - 1);
500: recycled.remove(size - 1);
501: }
502: }
503: if (session != null)
504: session.setManager(this );
505: else
506: session = new StandardSession(this );
507:
508: // Initialize the properties of the new session and return it
509: session.setNew(true);
510: session.setValid(true);
511: session.setCreationTime(System.currentTimeMillis());
512: session.setMaxInactiveInterval(this .maxInactiveInterval);
513: String sessionId = generateSessionId();
514: String jvmRoute = getJvmRoute();
515: // @todo Move appending of jvmRoute generateSessionId()???
516: if (jvmRoute != null) {
517: sessionId += '.' + jvmRoute;
518: session.setId(sessionId);
519: }
520: /*
521: synchronized (sessions) {
522: while (sessions.get(sessionId) != null) // Guarantee uniqueness
523: sessionId = generateSessionId();
524: }
525: */
526: session.setId(sessionId);
527:
528: return (session);
529:
530: }
531:
532: /**
533: * Return the active Session, associated with this Manager, with the
534: * specified session id (if any); otherwise return <code>null</code>.
535: *
536: * @param id The session id for the session to be returned
537: *
538: * @exception IllegalStateException if a new session cannot be
539: * instantiated for any reason
540: * @exception IOException if an input/output error occurs while
541: * processing this request
542: */
543: public Session findSession(String id) throws IOException {
544:
545: if (id == null)
546: return (null);
547: synchronized (sessions) {
548: Session session = (Session) sessions.get(id);
549: return (session);
550: }
551:
552: }
553:
554: /**
555: * Return the set of active Sessions associated with this Manager.
556: * If this Manager has no active Sessions, a zero-length array is returned.
557: */
558: public Session[] findSessions() {
559:
560: Session results[] = null;
561: synchronized (sessions) {
562: results = new Session[sessions.size()];
563: results = (Session[]) sessions.values().toArray(results);
564: }
565: return (results);
566:
567: }
568:
569: /**
570: * Remove this Session from the active Sessions for this Manager.
571: *
572: * @param session Session to be removed
573: */
574: public void remove(Session session) {
575:
576: synchronized (sessions) {
577: sessions.remove(session.getId());
578: }
579:
580: }
581:
582: /**
583: * Remove a property change listener from this component.
584: *
585: * @param listener The listener to remove
586: */
587: public void removePropertyChangeListener(
588: PropertyChangeListener listener) {
589:
590: support.removePropertyChangeListener(listener);
591:
592: }
593:
594: // ------------------------------------------------------ Protected Methods
595:
596: /**
597: * Generate and return a new session identifier.
598: */
599: protected synchronized String generateSessionId() {
600:
601: // Generate a byte array containing a session identifier
602: Random random = getRandom();
603: byte bytes[] = new byte[SESSION_ID_BYTES];
604: getRandom().nextBytes(bytes);
605: bytes = getDigest().digest(bytes);
606:
607: // Render the result as a String of hexadecimal digits
608: StringBuffer result = new StringBuffer();
609: for (int i = 0; i < bytes.length; i++) {
610: byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
611: byte b2 = (byte) (bytes[i] & 0x0f);
612: if (b1 < 10)
613: result.append((char) ('0' + b1));
614: else
615: result.append((char) ('A' + (b1 - 10)));
616: if (b2 < 10)
617: result.append((char) ('0' + b2));
618: else
619: result.append((char) ('A' + (b2 - 10)));
620: }
621: return (result.toString());
622:
623: }
624:
625: // ------------------------------------------------------ Protected Methods
626:
627: /**
628: * Retrieve the enclosing Engine for this Manager.
629: *
630: * @return an Engine object (or null).
631: */
632: public Engine getEngine() {
633: Engine e = null;
634: for (Container c = getContainer(); e == null && c != null; c = c
635: .getParent()) {
636: if (c != null && c instanceof Engine) {
637: e = (Engine) c;
638: }
639: }
640: return e;
641: }
642:
643: /**
644: * Retrieve the JvmRoute for the enclosing Engine.
645: * @return the JvmRoute or null.
646: */
647: public String getJvmRoute() {
648: Engine e = getEngine();
649: return e == null ? null : e.getJvmRoute();
650: }
651:
652: // -------------------------------------------------------- Package Methods
653:
654: /**
655: * Log a message on the Logger associated with our Container (if any).
656: *
657: * @param message Message to be logged
658: */
659: void log(String message) {
660:
661: Logger logger = null;
662: if (container != null)
663: logger = container.getLogger();
664: if (logger != null)
665: logger.log(getName() + "[" + container.getName() + "]: "
666: + message);
667: else {
668: String containerName = null;
669: if (container != null)
670: containerName = container.getName();
671: System.out.println(getName() + "[" + containerName + "]: "
672: + message);
673: }
674:
675: }
676:
677: /**
678: * Log a message on the Logger associated with our Container (if any).
679: *
680: * @param message Message to be logged
681: * @param throwable Associated exception
682: */
683: void log(String message, Throwable throwable) {
684:
685: Logger logger = null;
686: if (container != null)
687: logger = container.getLogger();
688: if (logger != null)
689: logger.log(getName() + "[" + container.getName() + "] "
690: + message, throwable);
691: else {
692: String containerName = null;
693: if (container != null)
694: containerName = container.getName();
695: System.out.println(getName() + "[" + containerName + "]: "
696: + message);
697: throwable.printStackTrace(System.out);
698: }
699:
700: }
701:
702: /**
703: * Add this Session to the recycle collection for this Manager.
704: *
705: * @param session Session to be recycled
706: */
707: void recycle(Session session) {
708:
709: synchronized (recycled) {
710: recycled.add(session);
711: }
712:
713: }
714:
715: }
|