001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.catalina.session;
018:
019: import java.beans.PropertyChangeEvent;
020: import java.beans.PropertyChangeListener;
021: import java.io.BufferedInputStream;
022: import java.io.BufferedOutputStream;
023: import java.io.File;
024: import java.io.FileInputStream;
025: import java.io.FileNotFoundException;
026: import java.io.FileOutputStream;
027: import java.io.IOException;
028: import java.io.ObjectInputStream;
029: import java.io.ObjectOutputStream;
030: import java.security.AccessController;
031: import java.security.PrivilegedActionException;
032: import java.security.PrivilegedExceptionAction;
033: import java.util.ArrayList;
034: import java.util.Iterator;
035: import javax.servlet.ServletContext;
036: import org.apache.catalina.Container;
037: import org.apache.catalina.Context;
038: import org.apache.catalina.Globals;
039: import org.apache.catalina.Lifecycle;
040: import org.apache.catalina.LifecycleException;
041: import org.apache.catalina.LifecycleListener;
042: import org.apache.catalina.Loader;
043: import org.apache.catalina.Session;
044: import org.apache.catalina.util.CustomObjectInputStream;
045: import org.apache.catalina.util.LifecycleSupport;
046:
047: /**
048: * Standard implementation of the <b>Manager</b> interface that provides
049: * simple session persistence across restarts of this component (such as
050: * when the entire server is shut down and restarted, or when a particular
051: * web application is reloaded.
052: * <p>
053: * <b>IMPLEMENTATION NOTE</b>: Correct behavior of session storing and
054: * reloading depends upon external calls to the <code>start()</code> and
055: * <code>stop()</code> methods of this class at the correct times.
056: *
057: * @author Craig R. McClanahan
058: * @author Jean-Francois Arcand
059: * @version $Revision: 1.22 $ $Date: 2004/05/26 16:14:10 $
060: */
061:
062: public class StandardManager extends ManagerBase implements Lifecycle,
063: PropertyChangeListener {
064:
065: // ---------------------------------------------------- Security Classes
066: private class PrivilegedDoLoad implements PrivilegedExceptionAction {
067:
068: PrivilegedDoLoad() {
069: }
070:
071: public Object run() throws Exception {
072: doLoad();
073: return null;
074: }
075: }
076:
077: private class PrivilegedDoUnload implements
078: PrivilegedExceptionAction {
079:
080: PrivilegedDoUnload() {
081: }
082:
083: public Object run() throws Exception {
084: doUnload();
085: return null;
086: }
087:
088: }
089:
090: // ----------------------------------------------------- Instance Variables
091:
092: /**
093: * The descriptive information about this implementation.
094: */
095: protected static final String info = "StandardManager/1.0";
096:
097: /**
098: * The lifecycle event support for this component.
099: */
100: protected LifecycleSupport lifecycle = new LifecycleSupport(this );
101:
102: /**
103: * The maximum number of active Sessions allowed, or -1 for no limit.
104: */
105: protected int maxActiveSessions = -1;
106:
107: /**
108: * The descriptive name of this Manager implementation (for logging).
109: */
110: protected static String name = "StandardManager";
111:
112: /**
113: * Path name of the disk file in which active sessions are saved
114: * when we stop, and from which these sessions are loaded when we start.
115: * A <code>null</code> value indicates that no persistence is desired.
116: * If this pathname is relative, it will be resolved against the
117: * temporary working directory provided by our context, available via
118: * the <code>javax.servlet.context.tempdir</code> context attribute.
119: */
120: protected String pathname = "SESSIONS.ser";
121:
122: /**
123: * Has this component been started yet?
124: */
125: protected boolean started = false;
126:
127: /**
128: * Number of session creations that failed due to maxActiveSessions.
129: */
130: protected int rejectedSessions = 0;
131:
132: /**
133: * Number of sessions that expired.
134: */
135: protected int expiredSessions = 0;
136:
137: /**
138: * Processing time during session expiration.
139: */
140: protected long processingTime = 0;
141:
142: // ------------------------------------------------------------- Properties
143:
144: /**
145: * Set the Container with which this Manager has been associated. If
146: * it is a Context (the usual case), listen for changes to the session
147: * timeout property.
148: *
149: * @param container The associated Container
150: */
151: public void setContainer(Container container) {
152:
153: // De-register from the old Container (if any)
154: if ((this .container != null)
155: && (this .container instanceof Context))
156: ((Context) this .container)
157: .removePropertyChangeListener(this );
158:
159: // Default processing provided by our superclass
160: super .setContainer(container);
161:
162: // Register with the new Container (if any)
163: if ((this .container != null)
164: && (this .container instanceof Context)) {
165: setMaxInactiveInterval(((Context) this .container)
166: .getSessionTimeout() * 60);
167: ((Context) this .container).addPropertyChangeListener(this );
168: }
169:
170: }
171:
172: /**
173: * Return descriptive information about this Manager implementation and
174: * the corresponding version number, in the format
175: * <code><description>/<version></code>.
176: */
177: public String getInfo() {
178:
179: return (info);
180:
181: }
182:
183: /**
184: * Return the maximum number of active Sessions allowed, or -1 for
185: * no limit.
186: */
187: public int getMaxActiveSessions() {
188:
189: return (this .maxActiveSessions);
190:
191: }
192:
193: /** Number of session creations that failed due to maxActiveSessions
194: *
195: * @return The count
196: */
197: public int getRejectedSessions() {
198: return rejectedSessions;
199: }
200:
201: public void setRejectedSessions(int rejectedSessions) {
202: this .rejectedSessions = rejectedSessions;
203: }
204:
205: /** Number of sessions that expired.
206: *
207: * @return The count
208: */
209: public int getExpiredSessions() {
210: return expiredSessions;
211: }
212:
213: public void setExpiredSessions(int expiredSessions) {
214: this .expiredSessions = expiredSessions;
215: }
216:
217: public long getProcessingTime() {
218: return processingTime;
219: }
220:
221: public void setProcessingTime(long processingTime) {
222: this .processingTime = processingTime;
223: }
224:
225: /**
226: * Set the maximum number of actives Sessions allowed, or -1 for
227: * no limit.
228: *
229: * @param max The new maximum number of sessions
230: */
231: public void setMaxActiveSessions(int max) {
232:
233: int oldMaxActiveSessions = this .maxActiveSessions;
234: this .maxActiveSessions = max;
235: support.firePropertyChange("maxActiveSessions", new Integer(
236: oldMaxActiveSessions), new Integer(
237: this .maxActiveSessions));
238:
239: }
240:
241: /**
242: * Return the descriptive short name of this Manager implementation.
243: */
244: public String getName() {
245:
246: return (name);
247:
248: }
249:
250: /**
251: * Return the session persistence pathname, if any.
252: */
253: public String getPathname() {
254:
255: return (this .pathname);
256:
257: }
258:
259: /**
260: * Set the session persistence pathname to the specified value. If no
261: * persistence support is desired, set the pathname to <code>null</code>.
262: *
263: * @param pathname New session persistence pathname
264: */
265: public void setPathname(String pathname) {
266:
267: String oldPathname = this .pathname;
268: this .pathname = pathname;
269: support.firePropertyChange("pathname", oldPathname,
270: this .pathname);
271:
272: }
273:
274: // --------------------------------------------------------- Public Methods
275:
276: /**
277: * Construct and return a new session object, based on the default
278: * settings specified by this Manager's properties. The session
279: * id will be assigned by this method, and available via the getId()
280: * method of the returned session. If a new session cannot be created
281: * for any reason, return <code>null</code>.
282: *
283: * @exception IllegalStateException if a new session cannot be
284: * instantiated for any reason
285: */
286: public Session createSession() {
287:
288: if ((maxActiveSessions >= 0)
289: && (sessions.size() >= maxActiveSessions)) {
290: rejectedSessions++;
291: throw new IllegalStateException(sm
292: .getString("standardManager.createSession.ise"));
293: }
294:
295: return (super .createSession());
296:
297: }
298:
299: /**
300: * Load any currently active sessions that were previously unloaded
301: * to the appropriate persistence mechanism, if any. If persistence is not
302: * supported, this method returns without doing anything.
303: *
304: * @exception ClassNotFoundException if a serialized class cannot be
305: * found during the reload
306: * @exception IOException if an input/output error occurs
307: */
308: public void load() throws ClassNotFoundException, IOException {
309: if (System.getSecurityManager() != null) {
310: try {
311: AccessController.doPrivileged(new PrivilegedDoLoad());
312: } catch (PrivilegedActionException ex) {
313: Exception exception = ex.getException();
314: if (exception instanceof ClassNotFoundException) {
315: throw (ClassNotFoundException) exception;
316: } else if (exception instanceof IOException) {
317: throw (IOException) exception;
318: }
319: if (log.isDebugEnabled())
320: log.debug("Unreported exception in load() "
321: + exception);
322: }
323: } else {
324: doLoad();
325: }
326: }
327:
328: /**
329: * Load any currently active sessions that were previously unloaded
330: * to the appropriate persistence mechanism, if any. If persistence is not
331: * supported, this method returns without doing anything.
332: *
333: * @exception ClassNotFoundException if a serialized class cannot be
334: * found during the reload
335: * @exception IOException if an input/output error occurs
336: */
337: protected void doLoad() throws ClassNotFoundException, IOException {
338: if (log.isDebugEnabled())
339: log.debug("Start: Loading persisted sessions");
340:
341: // Initialize our internal data structures
342: sessions.clear();
343:
344: // Open an input stream to the specified pathname, if any
345: File file = file();
346: if (file == null)
347: return;
348: if (log.isDebugEnabled())
349: log
350: .debug(sm.getString("standardManager.loading",
351: pathname));
352: FileInputStream fis = null;
353: ObjectInputStream ois = null;
354: Loader loader = null;
355: ClassLoader classLoader = null;
356: try {
357: fis = new FileInputStream(file.getAbsolutePath());
358: BufferedInputStream bis = new BufferedInputStream(fis);
359: if (container != null)
360: loader = container.getLoader();
361: if (loader != null)
362: classLoader = loader.getClassLoader();
363: if (classLoader != null) {
364: if (log.isDebugEnabled())
365: log
366: .debug("Creating custom object input stream for class loader ");
367: ois = new CustomObjectInputStream(bis, classLoader);
368: } else {
369: if (log.isDebugEnabled())
370: log.debug("Creating standard object input stream");
371: ois = new ObjectInputStream(bis);
372: }
373: } catch (FileNotFoundException e) {
374: if (log.isDebugEnabled())
375: log.debug("No persisted data file found");
376: return;
377: } catch (IOException e) {
378: log
379: .error(sm.getString("standardManager.loading.ioe",
380: e), e);
381: if (ois != null) {
382: try {
383: ois.close();
384: } catch (IOException f) {
385: ;
386: }
387: ois = null;
388: }
389: throw e;
390: }
391:
392: // Load the previously unloaded active sessions
393: synchronized (sessions) {
394: try {
395: Integer count = (Integer) ois.readObject();
396: int n = count.intValue();
397: if (log.isDebugEnabled())
398: log.debug("Loading " + n + " persisted sessions");
399: for (int i = 0; i < n; i++) {
400: StandardSession session = getNewSession();
401: session.readObjectData(ois);
402: session.setManager(this );
403: sessions.put(session.getId(), session);
404: session.activate();
405: session.endAccess();
406: }
407: } catch (ClassNotFoundException e) {
408: log.error(sm.getString("standardManager.loading.cnfe",
409: e), e);
410: if (ois != null) {
411: try {
412: ois.close();
413: } catch (IOException f) {
414: ;
415: }
416: ois = null;
417: }
418: throw e;
419: } catch (IOException e) {
420: log
421: .error(sm.getString(
422: "standardManager.loading.ioe", e), e);
423: if (ois != null) {
424: try {
425: ois.close();
426: } catch (IOException f) {
427: ;
428: }
429: ois = null;
430: }
431: throw e;
432: } finally {
433: // Close the input stream
434: try {
435: if (ois != null)
436: ois.close();
437: } catch (IOException f) {
438: // ignored
439: }
440:
441: // Delete the persistent storage file
442: if (file != null && file.exists())
443: file.delete();
444: }
445: }
446:
447: if (log.isDebugEnabled())
448: log.debug("Finish: Loading persisted sessions");
449: }
450:
451: /**
452: * Save any currently active sessions in the appropriate persistence
453: * mechanism, if any. If persistence is not supported, this method
454: * returns without doing anything.
455: *
456: * @exception IOException if an input/output error occurs
457: */
458: public void unload() throws IOException {
459: if (System.getSecurityManager() != null) {
460: try {
461: AccessController.doPrivileged(new PrivilegedDoUnload());
462: } catch (PrivilegedActionException ex) {
463: Exception exception = ex.getException();
464: if (exception instanceof IOException) {
465: throw (IOException) exception;
466: }
467: if (log.isDebugEnabled())
468: log.debug("Unreported exception in unLoad() "
469: + exception);
470: }
471: } else {
472: doUnload();
473: }
474: }
475:
476: /**
477: * Save any currently active sessions in the appropriate persistence
478: * mechanism, if any. If persistence is not supported, this method
479: * returns without doing anything.
480: *
481: * @exception IOException if an input/output error occurs
482: */
483: protected void doUnload() throws IOException {
484:
485: if (log.isDebugEnabled())
486: log.debug("Unloading persisted sessions");
487:
488: // Open an output stream to the specified pathname, if any
489: File file = file();
490: if (file == null)
491: return;
492: if (log.isDebugEnabled())
493: log.debug(sm.getString("standardManager.unloading",
494: pathname));
495: FileOutputStream fos = null;
496: ObjectOutputStream oos = null;
497: try {
498: fos = new FileOutputStream(file.getAbsolutePath());
499: oos = new ObjectOutputStream(new BufferedOutputStream(fos));
500: } catch (IOException e) {
501: log.error(sm.getString("standardManager.unloading.ioe", e),
502: e);
503: if (oos != null) {
504: try {
505: oos.close();
506: } catch (IOException f) {
507: ;
508: }
509: oos = null;
510: }
511: throw e;
512: }
513:
514: // Write the number of active sessions, followed by the details
515: ArrayList list = new ArrayList();
516: synchronized (sessions) {
517: if (log.isDebugEnabled())
518: log.debug("Unloading " + sessions.size() + " sessions");
519: try {
520: oos.writeObject(new Integer(sessions.size()));
521: Iterator elements = sessions.values().iterator();
522: while (elements.hasNext()) {
523: StandardSession session = (StandardSession) elements
524: .next();
525: list.add(session);
526: ((StandardSession) session).passivate();
527: session.writeObjectData(oos);
528: }
529: } catch (IOException e) {
530: log.error(sm.getString("standardManager.unloading.ioe",
531: e), e);
532: if (oos != null) {
533: try {
534: oos.close();
535: } catch (IOException f) {
536: ;
537: }
538: oos = null;
539: }
540: throw e;
541: }
542: }
543:
544: // Flush and close the output stream
545: try {
546: oos.flush();
547: oos.close();
548: oos = null;
549: } catch (IOException e) {
550: if (oos != null) {
551: try {
552: oos.close();
553: } catch (IOException f) {
554: ;
555: }
556: oos = null;
557: }
558: throw e;
559: }
560:
561: // Expire all the sessions we just wrote
562: if (log.isDebugEnabled())
563: log
564: .debug("Expiring " + list.size()
565: + " persisted sessions");
566: Iterator expires = list.iterator();
567: while (expires.hasNext()) {
568: StandardSession session = (StandardSession) expires.next();
569: try {
570: session.expire(false);
571: } catch (Throwable t) {
572: ;
573: }
574: }
575:
576: if (log.isDebugEnabled())
577: log.debug("Unloading complete");
578:
579: }
580:
581: // ------------------------------------------------------ Lifecycle Methods
582:
583: /**
584: * Add a lifecycle event listener to this component.
585: *
586: * @param listener The listener to add
587: */
588: public void addLifecycleListener(LifecycleListener listener) {
589:
590: lifecycle.addLifecycleListener(listener);
591:
592: }
593:
594: /**
595: * Get the lifecycle listeners associated with this lifecycle. If this
596: * Lifecycle has no listeners registered, a zero-length array is returned.
597: */
598: public LifecycleListener[] findLifecycleListeners() {
599:
600: return lifecycle.findLifecycleListeners();
601:
602: }
603:
604: /**
605: * Remove a lifecycle event listener from this component.
606: *
607: * @param listener The listener to remove
608: */
609: public void removeLifecycleListener(LifecycleListener listener) {
610:
611: lifecycle.removeLifecycleListener(listener);
612:
613: }
614:
615: /**
616: * Prepare for the beginning of active use of the public methods of this
617: * component. This method should be called after <code>configure()</code>,
618: * and before any of the public methods of the component are utilized.
619: *
620: * @exception LifecycleException if this component detects a fatal error
621: * that prevents this component from being used
622: */
623: public void start() throws LifecycleException {
624:
625: if (!initialized)
626: init();
627:
628: // Validate and update our current component state
629: if (started) {
630: return;
631: }
632: lifecycle.fireLifecycleEvent(START_EVENT, null);
633: started = true;
634:
635: // Force initialization of the random number generator
636: if (log.isDebugEnabled())
637: log.debug("Force random number initialization starting");
638: String dummy = generateSessionId();
639: if (log.isDebugEnabled())
640: log.debug("Force random number initialization completed");
641:
642: // Load unloaded sessions, if any
643: try {
644: load();
645: } catch (Throwable t) {
646: log.error(sm.getString("standardManager.managerLoad"), t);
647: }
648:
649: }
650:
651: /**
652: * Gracefully terminate the active use of the public methods of this
653: * component. This method should be the last one called on a given
654: * instance of this component.
655: *
656: * @exception LifecycleException if this component detects a fatal error
657: * that needs to be reported
658: */
659: public void stop() throws LifecycleException {
660:
661: if (log.isDebugEnabled())
662: log.debug("Stopping");
663:
664: // Validate and update our current component state
665: if (!started)
666: throw new LifecycleException(sm
667: .getString("standardManager.notStarted"));
668: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
669: started = false;
670:
671: // Write out sessions
672: try {
673: unload();
674: } catch (IOException e) {
675: log.error(sm.getString("standardManager.managerUnload"), e);
676: }
677:
678: // Expire all active sessions
679: Session sessions[] = findSessions();
680: for (int i = 0; i < sessions.length; i++) {
681: StandardSession session = (StandardSession) sessions[i];
682: if (!session.isValid())
683: continue;
684: try {
685: session.expire();
686: } catch (Throwable t) {
687: ;
688: }
689: }
690:
691: // Require a new random number generator if we are restarted
692: this .random = null;
693:
694: if (initialized) {
695: destroy();
696: }
697: }
698:
699: // ----------------------------------------- PropertyChangeListener Methods
700:
701: /**
702: * Process property change events from our associated Context.
703: *
704: * @param event The property change event that has occurred
705: */
706: public void propertyChange(PropertyChangeEvent event) {
707:
708: // Validate the source of this event
709: if (!(event.getSource() instanceof Context))
710: return;
711: Context context = (Context) event.getSource();
712:
713: // Process a relevant property change
714: if (event.getPropertyName().equals("sessionTimeout")) {
715: try {
716: setMaxInactiveInterval(((Integer) event.getNewValue())
717: .intValue() * 60);
718: } catch (NumberFormatException e) {
719: log.error(sm.getString(
720: "standardManager.sessionTimeout", event
721: .getNewValue().toString()));
722: }
723: }
724:
725: }
726:
727: // ------------------------------------------------------ Protected Methods
728:
729: /**
730: * Return a File object representing the pathname to our
731: * persistence file, if any.
732: */
733: protected File file() {
734:
735: if ((pathname == null) || (pathname.length() == 0))
736: return (null);
737: File file = new File(pathname);
738: if (!file.isAbsolute()) {
739: if (container instanceof Context) {
740: ServletContext servletContext = ((Context) container)
741: .getServletContext();
742: File tempdir = (File) servletContext
743: .getAttribute(Globals.WORK_DIR_ATTR);
744: if (tempdir != null)
745: file = new File(tempdir, pathname);
746: }
747: }
748: // if (!file.isAbsolute())
749: // return (null);
750: return (file);
751:
752: }
753:
754: /**
755: * Invalidate all sessions that have expired.
756: */
757: public void processExpires() {
758:
759: long timeNow = System.currentTimeMillis();
760: Session sessions[] = findSessions();
761:
762: for (int i = 0; i < sessions.length; i++) {
763: StandardSession session = (StandardSession) sessions[i];
764: if (!session.isValid()) {
765: expiredSessions++;
766: }
767: }
768: long timeEnd = System.currentTimeMillis();
769: processingTime += (timeEnd - timeNow);
770:
771: }
772:
773: /**
774: * Implements the Manager interface, direct call to processExpires
775: */
776: public void backgroundProcess() {
777: processExpires();
778: }
779:
780: }
|