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