0001: /*
0002: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/session/PersistentManagerBase.java,v 1.8 2002/06/09 02:19:43 remm Exp $
0003: * $Revision: 1.8 $
0004: * $Date: 2002/06/09 02:19:43 $
0005: *
0006: * ====================================================================
0007: *
0008: * The Apache Software License, Version 1.1
0009: *
0010: * Copyright (c) 1999 The Apache Software Foundation. All rights
0011: * reserved.
0012: *
0013: * Redistribution and use in source and binary forms, with or without
0014: * modification, are permitted provided that the following conditions
0015: * are met:
0016: *
0017: * 1. Redistributions of source code must retain the above copyright
0018: * notice, this list of conditions and the following disclaimer.
0019: *
0020: * 2. Redistributions in binary form must reproduce the above copyright
0021: * notice, this list of conditions and the following disclaimer in
0022: * the documentation and/or other materials provided with the
0023: * distribution.
0024: *
0025: * 3. The end-user documentation included with the redistribution, if
0026: * any, must include the following acknowlegement:
0027: * "This product includes software developed by the
0028: * Apache Software Foundation (http://www.apache.org/)."
0029: * Alternately, this acknowlegement may appear in the software itself,
0030: * if and wherever such third-party acknowlegements normally appear.
0031: *
0032: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
0033: * Foundation" must not be used to endorse or promote products derived
0034: * from this software without prior written permission. For written
0035: * permission, please contact apache@apache.org.
0036: *
0037: * 5. Products derived from this software may not be called "Apache"
0038: * nor may "Apache" appear in their names without prior written
0039: * permission of the Apache Group.
0040: *
0041: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0042: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0043: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0044: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0045: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0046: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0047: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0048: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0049: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0050: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0051: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0052: * SUCH DAMAGE.
0053: * ====================================================================
0054: *
0055: * This software consists of voluntary contributions made by many
0056: * individuals on behalf of the Apache Software Foundation. For more
0057: * information on the Apache Software Foundation, please see
0058: * <http://www.apache.org/>.
0059: *
0060: * [Additional notices, if required by prior licensing conditions]
0061: *
0062: */
0063:
0064: package org.apache.catalina.session;
0065:
0066: import java.beans.PropertyChangeEvent;
0067: import java.beans.PropertyChangeListener;
0068: import java.beans.PropertyChangeSupport;
0069: import java.io.BufferedInputStream;
0070: import java.io.BufferedOutputStream;
0071: import java.io.File;
0072: import java.io.FileInputStream;
0073: import java.io.FileNotFoundException;
0074: import java.io.FileOutputStream;
0075: import java.io.InputStream;
0076: import java.io.IOException;
0077: import java.io.ObjectInputStream;
0078: import java.io.ObjectOutputStream;
0079: import java.io.ObjectStreamClass;
0080: import java.util.ArrayList;
0081: import java.util.Iterator;
0082: import javax.servlet.ServletContext;
0083: import org.apache.catalina.Container;
0084: import org.apache.catalina.Context;
0085: import org.apache.catalina.Globals;
0086: import org.apache.catalina.Lifecycle;
0087: import org.apache.catalina.LifecycleEvent;
0088: import org.apache.catalina.LifecycleException;
0089: import org.apache.catalina.LifecycleListener;
0090: import org.apache.catalina.Loader;
0091: import org.apache.catalina.Manager;
0092: import org.apache.catalina.Session;
0093: import org.apache.catalina.Store;
0094: import org.apache.catalina.util.LifecycleSupport;
0095:
0096: /**
0097: * Extends the <b>ManagerBase</b> class to implement most of the
0098: * functionality required by a Manager which supports any kind of
0099: * persistence, even if onlyfor restarts.
0100: * <p>
0101: * <b>IMPLEMENTATION NOTE</b>: Correct behavior of session storing and
0102: * reloading depends upon external calls to the <code>start()</code> and
0103: * <code>stop()</code> methods of this class at the correct times.
0104: *
0105: * @author Craig R. McClanahan
0106: * @version $Revision: 1.8 $ $Date: 2002/06/09 02:19:43 $
0107: */
0108:
0109: public abstract class PersistentManagerBase extends ManagerBase
0110: implements Lifecycle, PropertyChangeListener, Runnable {
0111:
0112: // ----------------------------------------------------- Instance Variables
0113:
0114: /**
0115: * The interval (in seconds) between checks for expired sessions.
0116: */
0117: private int checkInterval = 60;
0118:
0119: /**
0120: * The descriptive information about this implementation.
0121: */
0122: private static final String info = "PersistentManagerBase/1.0";
0123:
0124: /**
0125: * The lifecycle event support for this component.
0126: */
0127: protected LifecycleSupport lifecycle = new LifecycleSupport(this );
0128:
0129: /**
0130: * The maximum number of active Sessions allowed, or -1 for no limit.
0131: */
0132: private int maxActiveSessions = -1;
0133:
0134: /**
0135: * The descriptive name of this Manager implementation (for logging).
0136: */
0137: protected static String name = "PersistentManagerBase";
0138:
0139: /**
0140: * Has this component been started yet?
0141: */
0142: private boolean started = false;
0143:
0144: /**
0145: * The background thread.
0146: */
0147: private Thread thread = null;
0148:
0149: /**
0150: * The background thread completion semaphore.
0151: */
0152: protected boolean threadDone = false;
0153:
0154: /**
0155: * Name to register for the background thread.
0156: */
0157: private String threadName = "PersistentManagerBase";
0158:
0159: /**
0160: * Store object which will manage the Session store.
0161: */
0162: private Store store = null;
0163:
0164: /**
0165: * Whether to save and reload sessions when the Manager <code>unload</code>
0166: * and <code>load</code> methods are called.
0167: */
0168: private boolean saveOnRestart = true;
0169:
0170: /**
0171: * How long a session must be idle before it should be backed up.
0172: * -1 means sessions won't be backed up.
0173: */
0174: private int maxIdleBackup = -1;
0175:
0176: /**
0177: * Minimum time a session must be idle before it is swapped to disk.
0178: * This overrides maxActiveSessions, to prevent thrashing if there are lots
0179: * of active sessions. Setting to -1 means it's ignored.
0180: */
0181: private int minIdleSwap = -1;
0182:
0183: /**
0184: * The maximum time a session may be idle before it should be swapped
0185: * to file just on general principle. Setting this to -1 means sessions
0186: * should not be forced out.
0187: */
0188: private int maxIdleSwap = -1;
0189:
0190: // ------------------------------------------------------------- Properties
0191:
0192: /**
0193: * Return the check interval (in seconds) for this Manager.
0194: */
0195: public int getCheckInterval() {
0196:
0197: return (this .checkInterval);
0198:
0199: }
0200:
0201: /**
0202: * Set the check interval (in seconds) for this Manager.
0203: *
0204: * @param checkInterval The new check interval
0205: */
0206: public void setCheckInterval(int checkInterval) {
0207:
0208: int oldCheckInterval = this .checkInterval;
0209: this .checkInterval = checkInterval;
0210: support.firePropertyChange("checkInterval", new Integer(
0211: oldCheckInterval), new Integer(this .checkInterval));
0212:
0213: }
0214:
0215: /**
0216: * Indicates how many seconds old a session can get, after its last
0217: * use in a request, before it should be backed up to the store. -1
0218: * means sessions are not backed up.
0219: */
0220: public int getMaxIdleBackup() {
0221:
0222: return maxIdleBackup;
0223:
0224: }
0225:
0226: /**
0227: * Sets the option to back sessions up to the Store after they
0228: * are used in a request. Sessions remain available in memory
0229: * after being backed up, so they are not passivated as they are
0230: * when swapped out. The value set indicates how old a session
0231: * may get (since its last use) before it must be backed up: -1
0232: * means sessions are not backed up.
0233: * <p>
0234: * Note that this is not a hard limit: sessions are checked
0235: * against this age limit periodically according to <b>checkInterval</b>.
0236: * This value should be considered to indicate when a session is
0237: * ripe for backing up.
0238: * <p>
0239: * So it is possible that a session may be idle for maxIdleBackup +
0240: * checkInterval seconds, plus the time it takes to handle other
0241: * session expiration, swapping, etc. tasks.
0242: *
0243: * @param backup The number of seconds after their last accessed
0244: * time when they should be written to the Store.
0245: */
0246: public void setMaxIdleBackup(int backup) {
0247:
0248: if (backup == this .maxIdleBackup)
0249: return;
0250: int oldBackup = this .maxIdleBackup;
0251: this .maxIdleBackup = backup;
0252: support.firePropertyChange("maxIdleBackup", new Integer(
0253: oldBackup), new Integer(this .maxIdleBackup));
0254:
0255: }
0256:
0257: /**
0258: * The time in seconds after which a session should be swapped out of
0259: * memory to disk.
0260: */
0261: public int getMaxIdleSwap() {
0262:
0263: return maxIdleSwap;
0264:
0265: }
0266:
0267: /**
0268: * Sets the time in seconds after which a session should be swapped out of
0269: * memory to disk.
0270: */
0271: public void setMaxIdleSwap(int max) {
0272:
0273: if (max == this .maxIdleSwap)
0274: return;
0275: int oldMaxIdleSwap = this .maxIdleSwap;
0276: this .maxIdleSwap = max;
0277: support.firePropertyChange("maxIdleSwap", new Integer(
0278: oldMaxIdleSwap), new Integer(this .maxIdleSwap));
0279:
0280: }
0281:
0282: /**
0283: * The minimum time in seconds that a session must be idle before
0284: * it can be swapped out of memory, or -1 if it can be swapped out
0285: * at any time.
0286: */
0287: public int getMinIdleSwap() {
0288:
0289: return minIdleSwap;
0290:
0291: }
0292:
0293: /**
0294: * Sets the minimum time in seconds that a session must be idle before
0295: * it can be swapped out of memory due to maxActiveSession. Set it to -1
0296: * if it can be swapped out at any time.
0297: */
0298: public void setMinIdleSwap(int min) {
0299:
0300: if (this .minIdleSwap == min)
0301: return;
0302: int oldMinIdleSwap = this .minIdleSwap;
0303: this .minIdleSwap = min;
0304: support.firePropertyChange("minIdleSwap", new Integer(
0305: oldMinIdleSwap), new Integer(this .minIdleSwap));
0306:
0307: }
0308:
0309: /**
0310: * Set the Container with which this Manager has been associated. If
0311: * it is a Context (the usual case), listen for changes to the session
0312: * timeout property.
0313: *
0314: * @param container The associated Container
0315: */
0316: public void setContainer(Container container) {
0317:
0318: // De-register from the old Container (if any)
0319: if ((this .container != null)
0320: && (this .container instanceof Context))
0321: ((Context) this .container)
0322: .removePropertyChangeListener(this );
0323:
0324: // Default processing provided by our superclass
0325: super .setContainer(container);
0326:
0327: // Register with the new Container (if any)
0328: if ((this .container != null)
0329: && (this .container instanceof Context)) {
0330: setMaxInactiveInterval(((Context) this .container)
0331: .getSessionTimeout() * 60);
0332: ((Context) this .container).addPropertyChangeListener(this );
0333: }
0334:
0335: }
0336:
0337: /**
0338: * Return descriptive information about this Manager implementation and
0339: * the corresponding version number, in the format
0340: * <code><description>/<version></code>.
0341: */
0342: public String getInfo() {
0343:
0344: return (this .info);
0345:
0346: }
0347:
0348: /**
0349: * Return the maximum number of active Sessions allowed, or -1 for
0350: * no limit.
0351: */
0352: public int getMaxActiveSessions() {
0353:
0354: return (this .maxActiveSessions);
0355:
0356: }
0357:
0358: /**
0359: * Set the maximum number of actives Sessions allowed, or -1 for
0360: * no limit.
0361: *
0362: * @param max The new maximum number of sessions
0363: */
0364: public void setMaxActiveSessions(int max) {
0365:
0366: int oldMaxActiveSessions = this .maxActiveSessions;
0367: this .maxActiveSessions = max;
0368: support.firePropertyChange("maxActiveSessions", new Integer(
0369: oldMaxActiveSessions), new Integer(
0370: this .maxActiveSessions));
0371:
0372: }
0373:
0374: /**
0375: * Return the descriptive short name of this Manager implementation.
0376: */
0377: public String getName() {
0378:
0379: return (name);
0380:
0381: }
0382:
0383: /**
0384: * Get the started status.
0385: */
0386: protected boolean isStarted() {
0387:
0388: return started;
0389:
0390: }
0391:
0392: /**
0393: * Set the started flag
0394: */
0395: protected void setStarted(boolean started) {
0396:
0397: this .started = started;
0398:
0399: }
0400:
0401: /**
0402: * Set the Store object which will manage persistent Session
0403: * storage for this Manager.
0404: *
0405: * @param store the associated Store
0406: */
0407: public void setStore(Store store) {
0408:
0409: this .store = store;
0410: store.setManager(this );
0411:
0412: }
0413:
0414: /**
0415: * Return the Store object which manages persistent Session
0416: * storage for this Manager.
0417: */
0418: public Store getStore() {
0419:
0420: return (this .store);
0421:
0422: }
0423:
0424: /**
0425: * Indicates whether sessions are saved when the Manager is shut down
0426: * properly. This requires the unload() method to be called.
0427: */
0428: public boolean getSaveOnRestart() {
0429:
0430: return saveOnRestart;
0431:
0432: }
0433:
0434: /**
0435: * Set the option to save sessions to the Store when the Manager is
0436: * shut down, then loaded when the Manager starts again. If set to
0437: * false, any sessions found in the Store may still be picked up when
0438: * the Manager is started again.
0439: *
0440: * @param save true if sessions should be saved on restart, false if
0441: * they should be ignored.
0442: */
0443: public void setSaveOnRestart(boolean saveOnRestart) {
0444:
0445: if (saveOnRestart == this .saveOnRestart)
0446: return;
0447:
0448: boolean oldSaveOnRestart = this .saveOnRestart;
0449: this .saveOnRestart = saveOnRestart;
0450: support.firePropertyChange("saveOnRestart", new Boolean(
0451: oldSaveOnRestart), new Boolean(this .saveOnRestart));
0452:
0453: }
0454:
0455: // --------------------------------------------------------- Public Methods
0456:
0457: /**
0458: * Clear all sessions from the Store.
0459: */
0460: public void clearStore() {
0461:
0462: if (store == null)
0463: return;
0464:
0465: try {
0466: store.clear();
0467: } catch (IOException e) {
0468: log("Exception clearing the Store: " + e);
0469: e.printStackTrace();
0470: }
0471:
0472: }
0473:
0474: /**
0475: * Called by the background thread after active sessions have
0476: * been checked for expiration, to allow sessions to be
0477: * swapped out, backed up, etc.
0478: */
0479: public void processPersistenceChecks() {
0480:
0481: processMaxIdleSwaps();
0482: processMaxActiveSwaps();
0483: processMaxIdleBackups();
0484:
0485: }
0486:
0487: /**
0488: * Return a new session object as long as the number of active
0489: * sessions does not exceed <b>maxActiveSessions</b>. If there
0490: * aren't too many active sessions, or if there is no limit,
0491: * a session is created or retrieved from the recycled pool.
0492: *
0493: * @exception IllegalStateException if a new session cannot be
0494: * instantiated for any reason
0495: */
0496: public Session createSession() {
0497:
0498: if ((maxActiveSessions >= 0)
0499: && (sessions.size() >= maxActiveSessions))
0500: throw new IllegalStateException(sm
0501: .getString("standardManager.createSession.ise"));
0502:
0503: return (super .createSession());
0504:
0505: }
0506:
0507: /**
0508: * Return the active Session, associated with this Manager, with the
0509: * specified session id (if any); otherwise return <code>null</code>.
0510: * This method checks the persistence store if persistence is enabled,
0511: * otherwise just uses the functionality from ManagerBase.
0512: *
0513: * @param id The session id for the session to be returned
0514: *
0515: * @exception IllegalStateException if a new session cannot be
0516: * instantiated for any reason
0517: * @exception IOException if an input/output error occurs while
0518: * processing this request
0519: */
0520: public Session findSession(String id) throws IOException {
0521:
0522: Session session = super .findSession(id);
0523: if (session != null)
0524: return (session);
0525:
0526: // See if the Session is in the Store
0527: session = swapIn(id);
0528: return (session);
0529:
0530: }
0531:
0532: /**
0533: * Load all sessions found in the persistence mechanism, assuming
0534: * they are marked as valid and have not passed their expiration
0535: * limit. If persistence is not supported, this method returns
0536: * without doing anything.
0537: * <p>
0538: * Note that by default, this method is not called by the MiddleManager
0539: * class. In order to use it, a subclass must specifically call it,
0540: * for example in the start() and/or processPersistenceChecks() methods.
0541: */
0542: public void load() {
0543:
0544: // Initialize our internal data structures
0545: recycled.clear();
0546: sessions.clear();
0547:
0548: if (store == null)
0549: return;
0550:
0551: String[] ids = null;
0552: try {
0553: ids = store.keys();
0554: } catch (IOException e) {
0555: log("Can't load sessions from store, " + e.getMessage(), e);
0556: return;
0557: }
0558:
0559: int n = ids.length;
0560: if (n == 0)
0561: return;
0562:
0563: if (debug >= 1)
0564: log(sm.getString("persistentManager.loading", String
0565: .valueOf(n)));
0566:
0567: for (int i = 0; i < n; i++)
0568: try {
0569: swapIn(ids[i]);
0570: } catch (IOException e) {
0571: log(
0572: "Failed load session from store, "
0573: + e.getMessage(), e);
0574: }
0575:
0576: }
0577:
0578: /**
0579: * Remove this Session from the active Sessions for this Manager,
0580: * and from the Store.
0581: *
0582: * @param session Session to be removed
0583: */
0584: public void remove(Session session) {
0585:
0586: super .remove(session);
0587:
0588: if (store != null)
0589: try {
0590: store.remove(session.getId());
0591: } catch (IOException e) {
0592: log("Exception removing session " + e.getMessage());
0593: e.printStackTrace();
0594: }
0595:
0596: }
0597:
0598: /**
0599: * Save all currently active sessions in the appropriate persistence
0600: * mechanism, if any. If persistence is not supported, this method
0601: * returns without doing anything.
0602: * <p>
0603: * Note that by default, this method is not called by the MiddleManager
0604: * class. In order to use it, a subclass must specifically call it,
0605: * for example in the stop() and/or processPersistenceChecks() methods.
0606: */
0607: public void unload() {
0608:
0609: if (store == null)
0610: return;
0611:
0612: Session sessions[] = findSessions();
0613: int n = sessions.length;
0614: if (n == 0)
0615: return;
0616:
0617: if (debug >= 1)
0618: log(sm.getString("persistentManager.unloading", String
0619: .valueOf(n)));
0620:
0621: for (int i = 0; i < n; i++)
0622: try {
0623: swapOut(sessions[i]);
0624: } catch (IOException e) {
0625: ; // This is logged in writeSession()
0626: }
0627:
0628: }
0629:
0630: // ------------------------------------------------------ Protected Methods
0631:
0632: /**
0633: * Look for a session in the Store and, if found, restore
0634: * it in the Manager's list of active sessions if appropriate.
0635: * The session will be removed from the Store after swapping
0636: * in, but will not be added to the active session list if it
0637: * is invalid or past its expiration.
0638: */
0639: protected Session swapIn(String id) throws IOException {
0640:
0641: if (store == null)
0642: return null;
0643:
0644: Session session = null;
0645: try {
0646: session = store.load(id);
0647: } catch (ClassNotFoundException e) {
0648: log(sm.getString("persistentManager.deserializeError", id,
0649: e));
0650: throw new IllegalStateException(sm.getString(
0651: "persistentManager.deserializeError", id, e));
0652: }
0653:
0654: if (session == null)
0655: return (null);
0656:
0657: if (!session.isValid()
0658: || isSessionStale(session, System.currentTimeMillis())) {
0659: log("session swapped in is invalid or expired");
0660: session.expire();
0661: store.remove(id);
0662: return (null);
0663: }
0664:
0665: if (debug > 2)
0666: log(sm.getString("persistentManager.swapIn", id));
0667:
0668: session.setManager(this );
0669: add(session);
0670: ((StandardSession) session).activate();
0671:
0672: return (session);
0673:
0674: }
0675:
0676: /**
0677: * Remove the session from the Manager's list of active
0678: * sessions and write it out to the Store. If the session
0679: * is past its expiration or invalid, this method does
0680: * nothing.
0681: *
0682: * @param session The Session to write out.
0683: */
0684: protected void swapOut(Session session) throws IOException {
0685:
0686: if (store == null || !session.isValid()
0687: || isSessionStale(session, System.currentTimeMillis()))
0688: return;
0689:
0690: ((StandardSession) session).passivate();
0691: writeSession(session);
0692: super .remove(session);
0693: session.recycle();
0694:
0695: }
0696:
0697: /**
0698: * Write the provided session to the Store without modifying
0699: * the copy in memory or triggering passivation events. Does
0700: * nothing if the session is invalid or past its expiration.
0701: */
0702: protected void writeSession(Session session) throws IOException {
0703:
0704: if (store == null || !session.isValid()
0705: || isSessionStale(session, System.currentTimeMillis()))
0706: return;
0707:
0708: try {
0709: store.save(session);
0710: } catch (IOException e) {
0711: log(sm.getString("persistentManager.serializeError",
0712: session.getId(), e));
0713: throw e;
0714: }
0715:
0716: }
0717:
0718: // ------------------------------------------------------ Lifecycle Methods
0719:
0720: /**
0721: * Add a lifecycle event listener to this component.
0722: *
0723: * @param listener The listener to add
0724: */
0725: public void addLifecycleListener(LifecycleListener listener) {
0726:
0727: lifecycle.addLifecycleListener(listener);
0728:
0729: }
0730:
0731: /**
0732: * Get the lifecycle listeners associated with this lifecycle. If this
0733: * Lifecycle has no listeners registered, a zero-length array is returned.
0734: */
0735: public LifecycleListener[] findLifecycleListeners() {
0736:
0737: return lifecycle.findLifecycleListeners();
0738:
0739: }
0740:
0741: /**
0742: * Remove a lifecycle event listener from this component.
0743: *
0744: * @param listener The listener to remove
0745: */
0746: public void removeLifecycleListener(LifecycleListener listener) {
0747:
0748: lifecycle.removeLifecycleListener(listener);
0749:
0750: }
0751:
0752: /**
0753: * Prepare for the beginning of active use of the public methods of this
0754: * component. This method should be called after <code>configure()</code>,
0755: * and before any of the public methods of the component are utilized.
0756: *
0757: * @exception LifecycleException if this component detects a fatal error
0758: * that prevents this component from being used
0759: */
0760: public void start() throws LifecycleException {
0761:
0762: if (debug >= 1)
0763: log("Starting");
0764:
0765: // Validate and update our current component state
0766: if (started)
0767: throw new LifecycleException(sm
0768: .getString("standardManager.alreadyStarted"));
0769: lifecycle.fireLifecycleEvent(START_EVENT, null);
0770: started = true;
0771:
0772: // Force initialization of the random number generator
0773: if (debug >= 1)
0774: log("Force random number initialization starting");
0775: String dummy = generateSessionId();
0776: if (debug >= 1)
0777: log("Force random number initialization completed");
0778:
0779: if (store == null)
0780: log("No Store configured, persistence disabled");
0781: else if (store instanceof Lifecycle)
0782: ((Lifecycle) store).start();
0783:
0784: // Start the background reaper thread
0785: threadStart();
0786:
0787: }
0788:
0789: /**
0790: * Gracefully terminate the active use of the public methods of this
0791: * component. This method should be the last one called on a given
0792: * instance of this component.
0793: *
0794: * @exception LifecycleException if this component detects a fatal error
0795: * that needs to be reported
0796: */
0797: public void stop() throws LifecycleException {
0798:
0799: if (debug >= 1)
0800: log("Stopping");
0801:
0802: // Validate and update our current component state
0803: if (!isStarted())
0804: throw new LifecycleException(sm
0805: .getString("standardManager.notStarted"));
0806: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
0807: setStarted(false);
0808:
0809: // Stop the background reaper thread
0810: threadStop();
0811:
0812: if (getStore() != null && saveOnRestart) {
0813: unload();
0814: } else {
0815: // Expire all active sessions
0816: Session sessions[] = findSessions();
0817: for (int i = 0; i < sessions.length; i++) {
0818: StandardSession session = (StandardSession) sessions[i];
0819: if (!session.isValid())
0820: continue;
0821: session.expire();
0822: }
0823: }
0824:
0825: if (getStore() != null && getStore() instanceof Lifecycle)
0826: ((Lifecycle) getStore()).stop();
0827:
0828: // Require a new random number generator if we are restarted
0829: this .random = null;
0830:
0831: }
0832:
0833: // ----------------------------------------- PropertyChangeListener Methods
0834:
0835: /**
0836: * Process property change events from our associated Context.
0837: *
0838: * @param event The property change event that has occurred
0839: */
0840: public void propertyChange(PropertyChangeEvent event) {
0841:
0842: // Validate the source of this event
0843: if (!(event.getSource() instanceof Context))
0844: return;
0845: Context context = (Context) event.getSource();
0846:
0847: // Process a relevant property change
0848: if (event.getPropertyName().equals("sessionTimeout")) {
0849: try {
0850: setMaxInactiveInterval(((Integer) event.getNewValue())
0851: .intValue() * 60);
0852: } catch (NumberFormatException e) {
0853: log(sm.getString("standardManager.sessionTimeout",
0854: event.getNewValue().toString()));
0855: }
0856: }
0857:
0858: }
0859:
0860: // -------------------------------------------------------- Private Methods
0861:
0862: /**
0863: * Indicate whether the session has been idle for longer
0864: * than its expiration date as of the supplied time.
0865: *
0866: * FIXME: Probably belongs in the Session class.
0867: */
0868: protected boolean isSessionStale(Session session, long timeNow) {
0869:
0870: int maxInactiveInterval = session.getMaxInactiveInterval();
0871: if (maxInactiveInterval >= 0) {
0872: int timeIdle = // Truncate, do not round up
0873: (int) ((timeNow - session.getLastAccessedTime()) / 1000L);
0874: if (timeIdle >= maxInactiveInterval)
0875: return true;
0876: }
0877:
0878: return false;
0879:
0880: }
0881:
0882: /**
0883: * Invalidate all sessions that have expired.
0884: */
0885: protected void processExpires() {
0886:
0887: if (!started)
0888: return;
0889:
0890: long timeNow = System.currentTimeMillis();
0891: Session sessions[] = findSessions();
0892:
0893: for (int i = 0; i < sessions.length; i++) {
0894: StandardSession session = (StandardSession) sessions[i];
0895: if (!session.isValid())
0896: continue;
0897: if (isSessionStale(session, timeNow))
0898: session.expire();
0899: }
0900:
0901: }
0902:
0903: /**
0904: * Swap idle sessions out to Store if they are idle too long.
0905: */
0906: protected void processMaxIdleSwaps() {
0907:
0908: if (!isStarted() || maxIdleSwap < 0)
0909: return;
0910:
0911: Session sessions[] = findSessions();
0912: long timeNow = System.currentTimeMillis();
0913:
0914: // Swap out all sessions idle longer than maxIdleSwap
0915: // FIXME: What's preventing us from mangling a session during
0916: // a request?
0917: if (maxIdleSwap >= 0) {
0918: for (int i = 0; i < sessions.length; i++) {
0919: StandardSession session = (StandardSession) sessions[i];
0920: if (!session.isValid())
0921: continue;
0922: int timeIdle = // Truncate, do not round up
0923: (int) ((timeNow - session.getLastAccessedTime()) / 1000L);
0924: if (timeIdle > maxIdleSwap && timeIdle > minIdleSwap) {
0925: if (debug > 1)
0926: log(sm.getString(
0927: "persistentManager.swapMaxIdle",
0928: session.getId(), new Integer(timeIdle)));
0929: try {
0930: swapOut(session);
0931: } catch (IOException e) {
0932: ; // This is logged in writeSession()
0933: }
0934: }
0935: }
0936: }
0937:
0938: }
0939:
0940: /**
0941: * Swap idle sessions out to Store if too many are active
0942: */
0943: protected void processMaxActiveSwaps() {
0944:
0945: if (!isStarted() || getMaxActiveSessions() < 0)
0946: return;
0947:
0948: Session sessions[] = findSessions();
0949:
0950: // FIXME: Smarter algorithm (LRU)
0951: if (getMaxActiveSessions() >= sessions.length)
0952: return;
0953:
0954: if (debug > 0)
0955: log(sm.getString("persistentManager.tooManyActive",
0956: new Integer(sessions.length)));
0957:
0958: int toswap = sessions.length - getMaxActiveSessions();
0959: long timeNow = System.currentTimeMillis();
0960:
0961: for (int i = 0; i < sessions.length && toswap > 0; i++) {
0962: int timeIdle = // Truncate, do not round up
0963: (int) ((timeNow - sessions[i].getLastAccessedTime()) / 1000L);
0964: if (timeIdle > minIdleSwap) {
0965: if (debug > 1)
0966: log(sm.getString(
0967: "persistentManager.swapTooManyActive",
0968: sessions[i].getId(), new Integer(timeIdle)));
0969: try {
0970: swapOut(sessions[i]);
0971: } catch (IOException e) {
0972: ; // This is logged in writeSession()
0973: }
0974: toswap--;
0975: }
0976: }
0977:
0978: }
0979:
0980: /**
0981: * Back up idle sessions.
0982: */
0983: protected void processMaxIdleBackups() {
0984:
0985: if (!isStarted() || maxIdleBackup < 0)
0986: return;
0987:
0988: Session sessions[] = findSessions();
0989: long timeNow = System.currentTimeMillis();
0990:
0991: // Back up all sessions idle longer than maxIdleBackup
0992: if (maxIdleBackup >= 0) {
0993: for (int i = 0; i < sessions.length; i++) {
0994: StandardSession session = (StandardSession) sessions[i];
0995: if (!session.isValid())
0996: continue;
0997: int timeIdle = // Truncate, do not round up
0998: (int) ((timeNow - session.getLastAccessedTime()) / 1000L);
0999: if (timeIdle > maxIdleBackup) {
1000: if (debug > 1)
1001: log(sm.getString(
1002: "persistentManager.backupMaxIdle",
1003: session.getId(), new Integer(timeIdle)));
1004:
1005: try {
1006: writeSession(session);
1007: } catch (IOException e) {
1008: ; // This is logged in writeSession()
1009: }
1010: }
1011: }
1012: }
1013:
1014: }
1015:
1016: /**
1017: * Sleep for the duration specified by the <code>checkInterval</code>
1018: * property.
1019: */
1020: protected void threadSleep() {
1021:
1022: try {
1023: Thread.sleep(checkInterval * 1000L);
1024: } catch (InterruptedException e) {
1025: ;
1026: }
1027:
1028: }
1029:
1030: /**
1031: * Start the background thread that will periodically check for
1032: * session timeouts.
1033: */
1034: protected void threadStart() {
1035:
1036: if (thread != null)
1037: return;
1038:
1039: threadDone = false;
1040: threadName = "StandardManager[" + container.getName() + "]";
1041: thread = new Thread(this , threadName);
1042: thread.setDaemon(true);
1043: thread.start();
1044:
1045: }
1046:
1047: /**
1048: * Stop the background thread that is periodically checking for
1049: * session timeouts.
1050: */
1051: protected void threadStop() {
1052:
1053: if (thread == null)
1054: return;
1055:
1056: threadDone = true;
1057: thread.interrupt();
1058: try {
1059: thread.join();
1060: } catch (InterruptedException e) {
1061: ;
1062: }
1063:
1064: thread = null;
1065:
1066: }
1067:
1068: // ------------------------------------------------------ Background Thread
1069:
1070: /**
1071: * The background thread that checks for session timeouts and shutdown.
1072: */
1073: public void run() {
1074:
1075: // Loop until the termination semaphore is set
1076: while (!threadDone) {
1077: threadSleep();
1078: processExpires();
1079: processPersistenceChecks();
1080: }
1081:
1082: }
1083:
1084: }
|