0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.catalina.session;
0019:
0020: import java.beans.PropertyChangeListener;
0021: import java.beans.PropertyChangeSupport;
0022: import java.io.DataInputStream;
0023: import java.io.File;
0024: import java.io.FileInputStream;
0025: import java.io.IOException;
0026: import java.lang.reflect.Method;
0027: import java.security.AccessController;
0028: import java.security.MessageDigest;
0029: import java.security.NoSuchAlgorithmException;
0030: import java.security.PrivilegedAction;
0031: import java.util.Date;
0032: import java.util.Enumeration;
0033: import java.util.HashMap;
0034: import java.util.Iterator;
0035: import java.util.Map;
0036: import java.util.Random;
0037: import java.util.concurrent.ConcurrentHashMap;
0038:
0039: import javax.management.MBeanRegistration;
0040: import javax.management.MBeanServer;
0041: import javax.management.ObjectName;
0042:
0043: import org.apache.catalina.Container;
0044: import org.apache.catalina.Engine;
0045: import org.apache.catalina.Globals;
0046: import org.apache.catalina.Manager;
0047: import org.apache.catalina.Session;
0048: import org.apache.catalina.core.StandardContext;
0049: import org.apache.catalina.core.StandardHost;
0050: import org.apache.catalina.util.StringManager;
0051: import org.apache.juli.logging.Log;
0052: import org.apache.juli.logging.LogFactory;
0053: import org.apache.tomcat.util.modeler.Registry;
0054:
0055: /**
0056: * Minimal implementation of the <b>Manager</b> interface that supports
0057: * no session persistence or distributable capabilities. This class may
0058: * be subclassed to create more sophisticated Manager implementations.
0059: *
0060: * @author Craig R. McClanahan
0061: * @version $Revision: 531901 $ $Date: 2007-04-24 14:24:34 +0200 (mar., 24 avr. 2007) $
0062: */
0063:
0064: public abstract class ManagerBase implements Manager, MBeanRegistration {
0065: protected Log log = LogFactory.getLog(ManagerBase.class);
0066:
0067: // ----------------------------------------------------- Instance Variables
0068:
0069: protected DataInputStream randomIS = null;
0070: protected String devRandomSource = "/dev/urandom";
0071:
0072: /**
0073: * The default message digest algorithm to use if we cannot use
0074: * the requested one.
0075: */
0076: protected static final String DEFAULT_ALGORITHM = "MD5";
0077:
0078: /**
0079: * The message digest algorithm to be used when generating session
0080: * identifiers. This must be an algorithm supported by the
0081: * <code>java.security.MessageDigest</code> class on your platform.
0082: */
0083: protected String algorithm = DEFAULT_ALGORITHM;
0084:
0085: /**
0086: * The Container with which this Manager is associated.
0087: */
0088: protected Container container;
0089:
0090: /**
0091: * Return the MessageDigest implementation to be used when
0092: * creating session identifiers.
0093: */
0094: protected MessageDigest digest = null;
0095:
0096: /**
0097: * The distributable flag for Sessions created by this Manager. If this
0098: * flag is set to <code>true</code>, any user attributes added to a
0099: * session controlled by this Manager must be Serializable.
0100: */
0101: protected boolean distributable;
0102:
0103: /**
0104: * A String initialization parameter used to increase the entropy of
0105: * the initialization of our random number generator.
0106: */
0107: protected String entropy = null;
0108:
0109: /**
0110: * The descriptive information string for this implementation.
0111: */
0112: private static final String info = "ManagerBase/1.0";
0113:
0114: /**
0115: * The default maximum inactive interval for Sessions created by
0116: * this Manager.
0117: */
0118: protected int maxInactiveInterval = 60;
0119:
0120: /**
0121: * The session id length of Sessions created by this Manager.
0122: */
0123: protected int sessionIdLength = 16;
0124:
0125: /**
0126: * The descriptive name of this Manager implementation (for logging).
0127: */
0128: protected static String name = "ManagerBase";
0129:
0130: /**
0131: * A random number generator to use when generating session identifiers.
0132: */
0133: protected Random random = null;
0134:
0135: /**
0136: * The Java class name of the random number generator class to be used
0137: * when generating session identifiers.
0138: */
0139: protected String randomClass = "java.security.SecureRandom";
0140:
0141: /**
0142: * The longest time (in seconds) that an expired session had been alive.
0143: */
0144: protected int sessionMaxAliveTime;
0145:
0146: /**
0147: * Average time (in seconds) that expired sessions had been alive.
0148: */
0149: protected int sessionAverageAliveTime;
0150:
0151: /**
0152: * Number of sessions that have expired.
0153: */
0154: protected int expiredSessions = 0;
0155:
0156: /**
0157: * The set of currently active Sessions for this Manager, keyed by
0158: * session identifier.
0159: */
0160: protected Map sessions = new ConcurrentHashMap();
0161:
0162: // Number of sessions created by this manager
0163: protected int sessionCounter = 0;
0164:
0165: protected int maxActive = 0;
0166:
0167: // number of duplicated session ids - anything >0 means we have problems
0168: protected int duplicates = 0;
0169:
0170: protected boolean initialized = false;
0171:
0172: /**
0173: * Processing time during session expiration.
0174: */
0175: protected long processingTime = 0;
0176:
0177: /**
0178: * Iteration count for background processing.
0179: */
0180: private int count = 0;
0181:
0182: /**
0183: * Frequency of the session expiration, and related manager operations.
0184: * Manager operations will be done once for the specified amount of
0185: * backgrondProcess calls (ie, the lower the amount, the most often the
0186: * checks will occur).
0187: */
0188: protected int processExpiresFrequency = 6;
0189:
0190: /**
0191: * The string manager for this package.
0192: */
0193: protected static StringManager sm = StringManager
0194: .getManager(Constants.Package);
0195:
0196: /**
0197: * The property change support for this component.
0198: */
0199: protected PropertyChangeSupport support = new PropertyChangeSupport(
0200: this );
0201:
0202: // ------------------------------------------------------------- Security classes
0203:
0204: private class PrivilegedSetRandomFile implements PrivilegedAction {
0205:
0206: public Object run() {
0207: try {
0208: File f = new File(devRandomSource);
0209: if (!f.exists())
0210: return null;
0211: randomIS = new DataInputStream(new FileInputStream(f));
0212: randomIS.readLong();
0213: if (log.isDebugEnabled())
0214: log.debug("Opening " + devRandomSource);
0215: return randomIS;
0216: } catch (IOException ex) {
0217: return null;
0218: }
0219: }
0220: }
0221:
0222: // ------------------------------------------------------------- Properties
0223:
0224: /**
0225: * Return the message digest algorithm for this Manager.
0226: */
0227: public String getAlgorithm() {
0228:
0229: return (this .algorithm);
0230:
0231: }
0232:
0233: /**
0234: * Set the message digest algorithm for this Manager.
0235: *
0236: * @param algorithm The new message digest algorithm
0237: */
0238: public void setAlgorithm(String algorithm) {
0239:
0240: String oldAlgorithm = this .algorithm;
0241: this .algorithm = algorithm;
0242: support.firePropertyChange("algorithm", oldAlgorithm,
0243: this .algorithm);
0244:
0245: }
0246:
0247: /**
0248: * Return the Container with which this Manager is associated.
0249: */
0250: public Container getContainer() {
0251:
0252: return (this .container);
0253:
0254: }
0255:
0256: /**
0257: * Set the Container with which this Manager is associated.
0258: *
0259: * @param container The newly associated Container
0260: */
0261: public void setContainer(Container container) {
0262:
0263: Container oldContainer = this .container;
0264: this .container = container;
0265: support.firePropertyChange("container", oldContainer,
0266: this .container);
0267: }
0268:
0269: /** Returns the name of the implementation class.
0270: */
0271: public String getClassName() {
0272: return this .getClass().getName();
0273: }
0274:
0275: /**
0276: * Return the MessageDigest object to be used for calculating
0277: * session identifiers. If none has been created yet, initialize
0278: * one the first time this method is called.
0279: */
0280: public synchronized MessageDigest getDigest() {
0281:
0282: if (this .digest == null) {
0283: long t1 = System.currentTimeMillis();
0284: if (log.isDebugEnabled())
0285: log.debug(sm
0286: .getString("managerBase.getting", algorithm));
0287: try {
0288: this .digest = MessageDigest.getInstance(algorithm);
0289: } catch (NoSuchAlgorithmException e) {
0290: log.error(
0291: sm.getString("managerBase.digest", algorithm),
0292: e);
0293: try {
0294: this .digest = MessageDigest
0295: .getInstance(DEFAULT_ALGORITHM);
0296: } catch (NoSuchAlgorithmException f) {
0297: log.error(sm.getString("managerBase.digest",
0298: DEFAULT_ALGORITHM), e);
0299: this .digest = null;
0300: }
0301: }
0302: if (log.isDebugEnabled())
0303: log.debug(sm.getString("managerBase.gotten"));
0304: long t2 = System.currentTimeMillis();
0305: if (log.isDebugEnabled())
0306: log.debug("getDigest() " + (t2 - t1));
0307: }
0308:
0309: return (this .digest);
0310:
0311: }
0312:
0313: /**
0314: * Return the distributable flag for the sessions supported by
0315: * this Manager.
0316: */
0317: public boolean getDistributable() {
0318:
0319: return (this .distributable);
0320:
0321: }
0322:
0323: /**
0324: * Set the distributable flag for the sessions supported by this
0325: * Manager. If this flag is set, all user data objects added to
0326: * sessions associated with this manager must implement Serializable.
0327: *
0328: * @param distributable The new distributable flag
0329: */
0330: public void setDistributable(boolean distributable) {
0331:
0332: boolean oldDistributable = this .distributable;
0333: this .distributable = distributable;
0334: support.firePropertyChange("distributable", new Boolean(
0335: oldDistributable), new Boolean(this .distributable));
0336:
0337: }
0338:
0339: /**
0340: * Return the entropy increaser value, or compute a semi-useful value
0341: * if this String has not yet been set.
0342: */
0343: public String getEntropy() {
0344:
0345: // Calculate a semi-useful value if this has not been set
0346: if (this .entropy == null) {
0347: // Use APR to get a crypto secure entropy value
0348: byte[] result = new byte[32];
0349: boolean apr = false;
0350: try {
0351: String methodName = "random";
0352: Class paramTypes[] = new Class[2];
0353: paramTypes[0] = result.getClass();
0354: paramTypes[1] = int.class;
0355: Object paramValues[] = new Object[2];
0356: paramValues[0] = result;
0357: paramValues[1] = new Integer(32);
0358: Method method = Class.forName(
0359: "org.apache.tomcat.jni.OS").getMethod(
0360: methodName, paramTypes);
0361: method.invoke(null, paramValues);
0362: apr = true;
0363: } catch (Throwable t) {
0364: // Ignore
0365: }
0366: if (apr) {
0367: setEntropy(new String(result));
0368: } else {
0369: setEntropy(this .toString());
0370: }
0371: }
0372:
0373: return (this .entropy);
0374:
0375: }
0376:
0377: /**
0378: * Set the entropy increaser value.
0379: *
0380: * @param entropy The new entropy increaser value
0381: */
0382: public void setEntropy(String entropy) {
0383:
0384: String oldEntropy = entropy;
0385: this .entropy = entropy;
0386: support.firePropertyChange("entropy", oldEntropy, this .entropy);
0387:
0388: }
0389:
0390: /**
0391: * Return descriptive information about this Manager implementation and
0392: * the corresponding version number, in the format
0393: * <code><description>/<version></code>.
0394: */
0395: public String getInfo() {
0396:
0397: return (info);
0398:
0399: }
0400:
0401: /**
0402: * Return the default maximum inactive interval (in seconds)
0403: * for Sessions created by this Manager.
0404: */
0405: public int getMaxInactiveInterval() {
0406:
0407: return (this .maxInactiveInterval);
0408:
0409: }
0410:
0411: /**
0412: * Set the default maximum inactive interval (in seconds)
0413: * for Sessions created by this Manager.
0414: *
0415: * @param interval The new default value
0416: */
0417: public void setMaxInactiveInterval(int interval) {
0418:
0419: int oldMaxInactiveInterval = this .maxInactiveInterval;
0420: this .maxInactiveInterval = interval;
0421: support.firePropertyChange("maxInactiveInterval", new Integer(
0422: oldMaxInactiveInterval), new Integer(
0423: this .maxInactiveInterval));
0424:
0425: }
0426:
0427: /**
0428: * Gets the session id length (in bytes) of Sessions created by
0429: * this Manager.
0430: *
0431: * @return The session id length
0432: */
0433: public int getSessionIdLength() {
0434:
0435: return (this .sessionIdLength);
0436:
0437: }
0438:
0439: /**
0440: * Sets the session id length (in bytes) for Sessions created by this
0441: * Manager.
0442: *
0443: * @param idLength The session id length
0444: */
0445: public void setSessionIdLength(int idLength) {
0446:
0447: int oldSessionIdLength = this .sessionIdLength;
0448: this .sessionIdLength = idLength;
0449: support.firePropertyChange("sessionIdLength", new Integer(
0450: oldSessionIdLength), new Integer(this .sessionIdLength));
0451:
0452: }
0453:
0454: /**
0455: * Return the descriptive short name of this Manager implementation.
0456: */
0457: public String getName() {
0458:
0459: return (name);
0460:
0461: }
0462:
0463: /**
0464: * Use /dev/random-type special device. This is new code, but may reduce
0465: * the big delay in generating the random.
0466: *
0467: * You must specify a path to a random generator file. Use /dev/urandom
0468: * for linux ( or similar ) systems. Use /dev/random for maximum security
0469: * ( it may block if not enough "random" exist ). You can also use
0470: * a pipe that generates random.
0471: *
0472: * The code will check if the file exists, and default to java Random
0473: * if not found. There is a significant performance difference, very
0474: * visible on the first call to getSession ( like in the first JSP )
0475: * - so use it if available.
0476: */
0477: public void setRandomFile(String s) {
0478: // as a hack, you can use a static file - and genarate the same
0479: // session ids ( good for strange debugging )
0480: if (Globals.IS_SECURITY_ENABLED) {
0481: randomIS = (DataInputStream) AccessController
0482: .doPrivileged(new PrivilegedSetRandomFile());
0483: } else {
0484: try {
0485: devRandomSource = s;
0486: File f = new File(devRandomSource);
0487: if (!f.exists())
0488: return;
0489: randomIS = new DataInputStream(new FileInputStream(f));
0490: randomIS.readLong();
0491: if (log.isDebugEnabled())
0492: log.debug("Opening " + devRandomSource);
0493: } catch (IOException ex) {
0494: try {
0495: randomIS.close();
0496: } catch (Exception e) {
0497: log.warn("Failed to close randomIS.");
0498: }
0499:
0500: randomIS = null;
0501: }
0502: }
0503: }
0504:
0505: public String getRandomFile() {
0506: return devRandomSource;
0507: }
0508:
0509: /**
0510: * Return the random number generator instance we should use for
0511: * generating session identifiers. If there is no such generator
0512: * currently defined, construct and seed a new one.
0513: */
0514: public Random getRandom() {
0515: if (this .random == null) {
0516: // Calculate the new random number generator seed
0517: long seed = System.currentTimeMillis();
0518: long t1 = seed;
0519: char entropy[] = getEntropy().toCharArray();
0520: for (int i = 0; i < entropy.length; i++) {
0521: long update = ((byte) entropy[i]) << ((i % 8) * 8);
0522: seed ^= update;
0523: }
0524: try {
0525: // Construct and seed a new random number generator
0526: Class clazz = Class.forName(randomClass);
0527: this .random = (Random) clazz.newInstance();
0528: this .random.setSeed(seed);
0529: } catch (Exception e) {
0530: // Fall back to the simple case
0531: log.error(sm.getString("managerBase.random",
0532: randomClass), e);
0533: this .random = new java.util.Random();
0534: this .random.setSeed(seed);
0535: }
0536: if (log.isDebugEnabled()) {
0537: long t2 = System.currentTimeMillis();
0538: if ((t2 - t1) > 100)
0539: log.debug(sm.getString("managerBase.seeding",
0540: randomClass)
0541: + " " + (t2 - t1));
0542: }
0543: }
0544:
0545: return (this .random);
0546:
0547: }
0548:
0549: /**
0550: * Return the random number generator class name.
0551: */
0552: public String getRandomClass() {
0553:
0554: return (this .randomClass);
0555:
0556: }
0557:
0558: /**
0559: * Set the random number generator class name.
0560: *
0561: * @param randomClass The new random number generator class name
0562: */
0563: public void setRandomClass(String randomClass) {
0564:
0565: String oldRandomClass = this .randomClass;
0566: this .randomClass = randomClass;
0567: support.firePropertyChange("randomClass", oldRandomClass,
0568: this .randomClass);
0569:
0570: }
0571:
0572: /**
0573: * Gets the number of sessions that have expired.
0574: *
0575: * @return Number of sessions that have expired
0576: */
0577: public int getExpiredSessions() {
0578: return expiredSessions;
0579: }
0580:
0581: /**
0582: * Sets the number of sessions that have expired.
0583: *
0584: * @param expiredSessions Number of sessions that have expired
0585: */
0586: public void setExpiredSessions(int expiredSessions) {
0587: this .expiredSessions = expiredSessions;
0588: }
0589:
0590: public long getProcessingTime() {
0591: return processingTime;
0592: }
0593:
0594: public void setProcessingTime(long processingTime) {
0595: this .processingTime = processingTime;
0596: }
0597:
0598: /**
0599: * Return the frequency of manager checks.
0600: */
0601: public int getProcessExpiresFrequency() {
0602:
0603: return (this .processExpiresFrequency);
0604:
0605: }
0606:
0607: /**
0608: * Set the manager checks frequency.
0609: *
0610: * @param processExpiresFrequency the new manager checks frequency
0611: */
0612: public void setProcessExpiresFrequency(int processExpiresFrequency) {
0613:
0614: if (processExpiresFrequency <= 0) {
0615: return;
0616: }
0617:
0618: int oldProcessExpiresFrequency = this .processExpiresFrequency;
0619: this .processExpiresFrequency = processExpiresFrequency;
0620: support.firePropertyChange("processExpiresFrequency",
0621: new Integer(oldProcessExpiresFrequency), new Integer(
0622: this .processExpiresFrequency));
0623:
0624: }
0625:
0626: // --------------------------------------------------------- Public Methods
0627:
0628: /**
0629: * Implements the Manager interface, direct call to processExpires
0630: */
0631: public void backgroundProcess() {
0632: count = (count + 1) % processExpiresFrequency;
0633: if (count == 0)
0634: processExpires();
0635: }
0636:
0637: /**
0638: * Invalidate all sessions that have expired.
0639: */
0640: public void processExpires() {
0641:
0642: long timeNow = System.currentTimeMillis();
0643: Session sessions[] = findSessions();
0644: int expireHere = 0;
0645:
0646: if (log.isDebugEnabled())
0647: log.debug("Start expire sessions " + getName() + " at "
0648: + timeNow + " sessioncount " + sessions.length);
0649: for (int i = 0; i < sessions.length; i++) {
0650: if (!sessions[i].isValid()) {
0651: expireHere++;
0652: }
0653: }
0654: long timeEnd = System.currentTimeMillis();
0655: if (log.isDebugEnabled())
0656: log.debug("End expire sessions " + getName()
0657: + " processingTime " + (timeEnd - timeNow)
0658: + " expired sessions: " + expireHere);
0659: processingTime += (timeEnd - timeNow);
0660:
0661: }
0662:
0663: public void destroy() {
0664: if (oname != null)
0665: Registry.getRegistry(null, null).unregisterComponent(oname);
0666: initialized = false;
0667: oname = null;
0668: }
0669:
0670: public void init() {
0671: if (initialized)
0672: return;
0673: initialized = true;
0674:
0675: log = LogFactory.getLog(ManagerBase.class);
0676:
0677: if (oname == null) {
0678: try {
0679: StandardContext ctx = (StandardContext) this
0680: .getContainer();
0681: Engine eng = (Engine) ctx.getParent().getParent();
0682: domain = ctx.getEngineName();
0683: distributable = ctx.getDistributable();
0684: StandardHost hst = (StandardHost) ctx.getParent();
0685: String path = ctx.getPath();
0686: if (path.equals("")) {
0687: path = "/";
0688: }
0689: oname = new ObjectName(domain + ":type=Manager,path="
0690: + path + ",host=" + hst.getName());
0691: Registry.getRegistry(null, null).registerComponent(
0692: this , oname, null);
0693: } catch (Exception e) {
0694: log.error("Error registering ", e);
0695: }
0696: }
0697:
0698: // Initialize random number generation
0699: getRandomBytes(new byte[16]);
0700:
0701: if (log.isDebugEnabled())
0702: log.debug("Registering " + oname);
0703:
0704: }
0705:
0706: /**
0707: * Add this Session to the set of active Sessions for this Manager.
0708: *
0709: * @param session Session to be added
0710: */
0711: public void add(Session session) {
0712:
0713: sessions.put(session.getIdInternal(), session);
0714: int size = sessions.size();
0715: if (size > maxActive) {
0716: maxActive = size;
0717: }
0718: }
0719:
0720: /**
0721: * Add a property change listener to this component.
0722: *
0723: * @param listener The listener to add
0724: */
0725: public void addPropertyChangeListener(
0726: PropertyChangeListener listener) {
0727:
0728: support.addPropertyChangeListener(listener);
0729:
0730: }
0731:
0732: /**
0733: * Construct and return a new session object, based on the default
0734: * settings specified by this Manager's properties. The session
0735: * id will be assigned by this method, and available via the getId()
0736: * method of the returned session. If a new session cannot be created
0737: * for any reason, return <code>null</code>.
0738: *
0739: * @exception IllegalStateException if a new session cannot be
0740: * instantiated for any reason
0741: * @deprecated
0742: */
0743: public Session createSession() {
0744: return createSession(null);
0745: }
0746:
0747: /**
0748: * Construct and return a new session object, based on the default
0749: * settings specified by this Manager's properties. The session
0750: * id specified will be used as the session id.
0751: * If a new session cannot be created for any reason, return
0752: * <code>null</code>.
0753: *
0754: * @param sessionId The session id which should be used to create the
0755: * new session; if <code>null</code>, a new session id will be
0756: * generated
0757: * @exception IllegalStateException if a new session cannot be
0758: * instantiated for any reason
0759: */
0760: public Session createSession(String sessionId) {
0761:
0762: // Recycle or create a Session instance
0763: Session session = createEmptySession();
0764:
0765: // Initialize the properties of the new session and return it
0766: session.setNew(true);
0767: session.setValid(true);
0768: session.setCreationTime(System.currentTimeMillis());
0769: session.setMaxInactiveInterval(this .maxInactiveInterval);
0770: if (sessionId == null) {
0771: sessionId = generateSessionId();
0772: // FIXME WHy we need no duplication check?
0773: /*
0774: synchronized (sessions) {
0775: while (sessions.get(sessionId) != null) { // Guarantee
0776: // uniqueness
0777: duplicates++;
0778: sessionId = generateSessionId();
0779: }
0780: }
0781: */
0782:
0783: // FIXME: Code to be used in case route replacement is needed
0784: /*
0785: } else {
0786: String jvmRoute = getJvmRoute();
0787: if (getJvmRoute() != null) {
0788: String requestJvmRoute = null;
0789: int index = sessionId.indexOf(".");
0790: if (index > 0) {
0791: requestJvmRoute = sessionId
0792: .substring(index + 1, sessionId.length());
0793: }
0794: if (requestJvmRoute != null && !requestJvmRoute.equals(jvmRoute)) {
0795: sessionId = sessionId.substring(0, index) + "." + jvmRoute;
0796: }
0797: }
0798: */
0799: }
0800: session.setId(sessionId);
0801: sessionCounter++;
0802:
0803: return (session);
0804:
0805: }
0806:
0807: /**
0808: * Get a session from the recycled ones or create a new empty one.
0809: * The PersistentManager manager does not need to create session data
0810: * because it reads it from the Store.
0811: */
0812: public Session createEmptySession() {
0813: return (getNewSession());
0814: }
0815:
0816: /**
0817: * Return the active Session, associated with this Manager, with the
0818: * specified session id (if any); otherwise return <code>null</code>.
0819: *
0820: * @param id The session id for the session to be returned
0821: *
0822: * @exception IllegalStateException if a new session cannot be
0823: * instantiated for any reason
0824: * @exception IOException if an input/output error occurs while
0825: * processing this request
0826: */
0827: public Session findSession(String id) throws IOException {
0828:
0829: if (id == null)
0830: return (null);
0831: return (Session) sessions.get(id);
0832:
0833: }
0834:
0835: /**
0836: * Return the set of active Sessions associated with this Manager.
0837: * If this Manager has no active Sessions, a zero-length array is returned.
0838: */
0839: public Session[] findSessions() {
0840:
0841: Session results[] = null;
0842: synchronized (sessions) {
0843: results = new Session[sessions.size()];
0844: results = (Session[]) sessions.values().toArray(results);
0845: }
0846: return (results);
0847:
0848: }
0849:
0850: /**
0851: * Remove this Session from the active Sessions for this Manager.
0852: *
0853: * @param session Session to be removed
0854: */
0855: public void remove(Session session) {
0856:
0857: sessions.remove(session.getIdInternal());
0858:
0859: }
0860:
0861: /**
0862: * Remove a property change listener from this component.
0863: *
0864: * @param listener The listener to remove
0865: */
0866: public void removePropertyChangeListener(
0867: PropertyChangeListener listener) {
0868:
0869: support.removePropertyChangeListener(listener);
0870:
0871: }
0872:
0873: // ------------------------------------------------------ Protected Methods
0874:
0875: /**
0876: * Get new session class to be used in the doLoad() method.
0877: */
0878: protected StandardSession getNewSession() {
0879: return new StandardSession(this );
0880: }
0881:
0882: protected void getRandomBytes(byte bytes[]) {
0883: // Generate a byte array containing a session identifier
0884: if (devRandomSource != null && randomIS == null) {
0885: setRandomFile(devRandomSource);
0886: }
0887: if (randomIS != null) {
0888: try {
0889: int len = randomIS.read(bytes);
0890: if (len == bytes.length) {
0891: return;
0892: }
0893: if (log.isDebugEnabled())
0894: log.debug("Got " + len + " " + bytes.length);
0895: } catch (Exception ex) {
0896: // Ignore
0897: }
0898: devRandomSource = null;
0899:
0900: try {
0901: randomIS.close();
0902: } catch (Exception e) {
0903: log.warn("Failed to close randomIS.");
0904: }
0905:
0906: randomIS = null;
0907: }
0908: getRandom().nextBytes(bytes);
0909: }
0910:
0911: /**
0912: * Generate and return a new session identifier.
0913: */
0914: protected synchronized String generateSessionId() {
0915:
0916: byte random[] = new byte[16];
0917: String jvmRoute = getJvmRoute();
0918: String result = null;
0919:
0920: // Render the result as a String of hexadecimal digits
0921: StringBuffer buffer = new StringBuffer();
0922: do {
0923: int resultLenBytes = 0;
0924: if (result != null) {
0925: buffer = new StringBuffer();
0926: duplicates++;
0927: }
0928:
0929: while (resultLenBytes < this .sessionIdLength) {
0930: getRandomBytes(random);
0931: random = getDigest().digest(random);
0932: for (int j = 0; j < random.length
0933: && resultLenBytes < this .sessionIdLength; j++) {
0934: byte b1 = (byte) ((random[j] & 0xf0) >> 4);
0935: byte b2 = (byte) (random[j] & 0x0f);
0936: if (b1 < 10)
0937: buffer.append((char) ('0' + b1));
0938: else
0939: buffer.append((char) ('A' + (b1 - 10)));
0940: if (b2 < 10)
0941: buffer.append((char) ('0' + b2));
0942: else
0943: buffer.append((char) ('A' + (b2 - 10)));
0944: resultLenBytes++;
0945: }
0946: }
0947: if (jvmRoute != null) {
0948: buffer.append('.').append(jvmRoute);
0949: }
0950: result = buffer.toString();
0951: } while (sessions.containsKey(result));
0952: return (result);
0953:
0954: }
0955:
0956: // ------------------------------------------------------ Protected Methods
0957:
0958: /**
0959: * Retrieve the enclosing Engine for this Manager.
0960: *
0961: * @return an Engine object (or null).
0962: */
0963: public Engine getEngine() {
0964: Engine e = null;
0965: for (Container c = getContainer(); e == null && c != null; c = c
0966: .getParent()) {
0967: if (c != null && c instanceof Engine) {
0968: e = (Engine) c;
0969: }
0970: }
0971: return e;
0972: }
0973:
0974: /**
0975: * Retrieve the JvmRoute for the enclosing Engine.
0976: * @return the JvmRoute or null.
0977: */
0978: public String getJvmRoute() {
0979: Engine e = getEngine();
0980: return e == null ? null : e.getJvmRoute();
0981: }
0982:
0983: // -------------------------------------------------------- Package Methods
0984:
0985: public void setSessionCounter(int sessionCounter) {
0986: this .sessionCounter = sessionCounter;
0987: }
0988:
0989: /**
0990: * Total sessions created by this manager.
0991: *
0992: * @return sessions created
0993: */
0994: public int getSessionCounter() {
0995: return sessionCounter;
0996: }
0997:
0998: /**
0999: * Number of duplicated session IDs generated by the random source.
1000: * Anything bigger than 0 means problems.
1001: *
1002: * @return The count of duplicates
1003: */
1004: public int getDuplicates() {
1005: return duplicates;
1006: }
1007:
1008: public void setDuplicates(int duplicates) {
1009: this .duplicates = duplicates;
1010: }
1011:
1012: /**
1013: * Returns the number of active sessions
1014: *
1015: * @return number of sessions active
1016: */
1017: public int getActiveSessions() {
1018: return sessions.size();
1019: }
1020:
1021: /**
1022: * Max number of concurrent active sessions
1023: *
1024: * @return The highest number of concurrent active sessions
1025: */
1026: public int getMaxActive() {
1027: return maxActive;
1028: }
1029:
1030: public void setMaxActive(int maxActive) {
1031: this .maxActive = maxActive;
1032: }
1033:
1034: /**
1035: * Gets the longest time (in seconds) that an expired session had been
1036: * alive.
1037: *
1038: * @return Longest time (in seconds) that an expired session had been
1039: * alive.
1040: */
1041: public int getSessionMaxAliveTime() {
1042: return sessionMaxAliveTime;
1043: }
1044:
1045: /**
1046: * Sets the longest time (in seconds) that an expired session had been
1047: * alive.
1048: *
1049: * @param sessionMaxAliveTime Longest time (in seconds) that an expired
1050: * session had been alive.
1051: */
1052: public void setSessionMaxAliveTime(int sessionMaxAliveTime) {
1053: this .sessionMaxAliveTime = sessionMaxAliveTime;
1054: }
1055:
1056: /**
1057: * Gets the average time (in seconds) that expired sessions had been
1058: * alive.
1059: *
1060: * @return Average time (in seconds) that expired sessions had been
1061: * alive.
1062: */
1063: public int getSessionAverageAliveTime() {
1064: return sessionAverageAliveTime;
1065: }
1066:
1067: /**
1068: * Sets the average time (in seconds) that expired sessions had been
1069: * alive.
1070: *
1071: * @param sessionAverageAliveTime Average time (in seconds) that expired
1072: * sessions had been alive.
1073: */
1074: public void setSessionAverageAliveTime(int sessionAverageAliveTime) {
1075: this .sessionAverageAliveTime = sessionAverageAliveTime;
1076: }
1077:
1078: /**
1079: * For debugging: return a list of all session ids currently active
1080: *
1081: */
1082: public String listSessionIds() {
1083: StringBuffer sb = new StringBuffer();
1084: Iterator keys = sessions.keySet().iterator();
1085: while (keys.hasNext()) {
1086: sb.append(keys.next()).append(" ");
1087: }
1088: return sb.toString();
1089: }
1090:
1091: /**
1092: * For debugging: get a session attribute
1093: *
1094: * @param sessionId
1095: * @param key
1096: * @return The attribute value, if found, null otherwise
1097: */
1098: public String getSessionAttribute(String sessionId, String key) {
1099: Session s = (Session) sessions.get(sessionId);
1100: if (s == null) {
1101: if (log.isInfoEnabled())
1102: log.info("Session not found " + sessionId);
1103: return null;
1104: }
1105: Object o = s.getSession().getAttribute(key);
1106: if (o == null)
1107: return null;
1108: return o.toString();
1109: }
1110:
1111: /**
1112: * Returns information about the session with the given session id.
1113: *
1114: * <p>The session information is organized as a HashMap, mapping
1115: * session attribute names to the String representation of their values.
1116: *
1117: * @param sessionId Session id
1118: *
1119: * @return HashMap mapping session attribute names to the String
1120: * representation of their values, or null if no session with the
1121: * specified id exists, or if the session does not have any attributes
1122: */
1123: public HashMap getSession(String sessionId) {
1124: Session s = (Session) sessions.get(sessionId);
1125: if (s == null) {
1126: if (log.isInfoEnabled()) {
1127: log.info("Session not found " + sessionId);
1128: }
1129: return null;
1130: }
1131:
1132: Enumeration ee = s.getSession().getAttributeNames();
1133: if (ee == null || !ee.hasMoreElements()) {
1134: return null;
1135: }
1136:
1137: HashMap map = new HashMap();
1138: while (ee.hasMoreElements()) {
1139: String attrName = (String) ee.nextElement();
1140: map.put(attrName, getSessionAttribute(sessionId, attrName));
1141: }
1142:
1143: return map;
1144: }
1145:
1146: public void expireSession(String sessionId) {
1147: Session s = (Session) sessions.get(sessionId);
1148: if (s == null) {
1149: if (log.isInfoEnabled())
1150: log.info("Session not found " + sessionId);
1151: return;
1152: }
1153: s.expire();
1154: }
1155:
1156: public String getLastAccessedTime(String sessionId) {
1157: Session s = (Session) sessions.get(sessionId);
1158: if (s == null) {
1159: log.info("Session not found " + sessionId);
1160: return "";
1161: }
1162: return new Date(s.getLastAccessedTime()).toString();
1163: }
1164:
1165: // -------------------- JMX and Registration --------------------
1166: protected String domain;
1167: protected ObjectName oname;
1168: protected MBeanServer mserver;
1169:
1170: public ObjectName getObjectName() {
1171: return oname;
1172: }
1173:
1174: public String getDomain() {
1175: return domain;
1176: }
1177:
1178: public ObjectName preRegister(MBeanServer server, ObjectName name)
1179: throws Exception {
1180: oname = name;
1181: mserver = server;
1182: domain = name.getDomain();
1183: return name;
1184: }
1185:
1186: public void postRegister(Boolean registrationDone) {
1187: }
1188:
1189: public void preDeregister() throws Exception {
1190: }
1191:
1192: public void postDeregister() {
1193: }
1194:
1195: }
|