001: /*
002: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/session/FileStore.java,v 1.7 2002/03/18 18:56:21 craigmcc Exp $
003: * $Revision: 1.7 $
004: * $Date: 2002/03/18 18:56:21 $
005: *
006: * ====================================================================
007: *
008: * The Apache Software License, Version 1.1
009: *
010: * Copyright (c) 1999 The Apache Software Foundation. All rights
011: * reserved.
012: *
013: * Redistribution and use in source and binary forms, with or without
014: * modification, are permitted provided that the following conditions
015: * are met:
016: *
017: * 1. Redistributions of source code must retain the above copyright
018: * notice, this list of conditions and the following disclaimer.
019: *
020: * 2. Redistributions in binary form must reproduce the above copyright
021: * notice, this list of conditions and the following disclaimer in
022: * the documentation and/or other materials provided with the
023: * distribution.
024: *
025: * 3. The end-user documentation included with the redistribution, if
026: * any, must include the following acknowlegement:
027: * "This product includes software developed by the
028: * Apache Software Foundation (http://www.apache.org/)."
029: * Alternately, this acknowlegement may appear in the software itself,
030: * if and wherever such third-party acknowlegements normally appear.
031: *
032: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
033: * Foundation" must not be used to endorse or promote products derived
034: * from this software without prior written permission. For written
035: * permission, please contact apache@apache.org.
036: *
037: * 5. Products derived from this software may not be called "Apache"
038: * nor may "Apache" appear in their names without prior written
039: * permission of the Apache Group.
040: *
041: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
042: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
043: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
044: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
045: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
046: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
047: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
048: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
049: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
050: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
051: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
052: * SUCH DAMAGE.
053: * ====================================================================
054: *
055: * This software consists of voluntary contributions made by many
056: * individuals on behalf of the Apache Software Foundation. For more
057: * information on the Apache Software Foundation, please see
058: * <http://www.apache.org/>.
059: *
060: * [Additional notices, if required by prior licensing conditions]
061: *
062: */
063:
064: package org.apache.catalina.session;
065:
066: import java.io.BufferedInputStream;
067: import java.io.BufferedOutputStream;
068: import java.io.File;
069: import java.io.FileInputStream;
070: import java.io.FileNotFoundException;
071: import java.io.FileOutputStream;
072: import java.io.IOException;
073: import java.io.InputStream;
074: import java.io.ObjectInputStream;
075: import java.io.ObjectOutputStream;
076: import java.io.ObjectStreamClass;
077: import java.io.Serializable;
078: import java.util.ArrayList;
079: import javax.servlet.ServletContext;
080: import org.apache.catalina.Context;
081: import org.apache.catalina.Globals;
082: import org.apache.catalina.Loader;
083: import org.apache.catalina.Session;
084: import org.apache.catalina.Store;
085: import org.apache.catalina.Container;
086: import org.apache.catalina.util.CustomObjectInputStream;
087:
088: /**
089: * Concrete implementation of the <b>Store</b> interface that utilizes
090: * a file per saved Session in a configured directory. Sessions that are
091: * saved are still subject to being expired based on inactivity.
092: *
093: * @author Craig R. McClanahan
094: * @version $Revision: 1.7 $ $Date: 2002/03/18 18:56:21 $
095: */
096:
097: public final class FileStore extends StoreBase implements Store {
098:
099: // ----------------------------------------------------- Constants
100:
101: /**
102: * The extension to use for serialized session filenames.
103: */
104: private static final String FILE_EXT = ".session";
105:
106: // ----------------------------------------------------- Instance Variables
107:
108: /**
109: * The pathname of the directory in which Sessions are stored.
110: * This may be an absolute pathname, or a relative path that is
111: * resolved against the temporary work directory for this application.
112: */
113: private String directory = ".";
114:
115: /**
116: * A File representing the directory in which Sessions are stored.
117: */
118: private File directoryFile = null;
119:
120: /**
121: * The descriptive information about this implementation.
122: */
123: private static final String info = "FileStore/1.0";
124:
125: /**
126: * Name to register for this Store, used for logging.
127: */
128: private static final String storeName = "fileStore";
129:
130: /**
131: * Name to register for the background thread.
132: */
133: private static final String threadName = "FileStore";
134:
135: // ------------------------------------------------------------- Properties
136:
137: /**
138: * Return the directory path for this Store.
139: */
140: public String getDirectory() {
141:
142: return (directory);
143:
144: }
145:
146: /**
147: * Set the directory path for this Store.
148: *
149: * @param path The new directory path
150: */
151: public void setDirectory(String path) {
152:
153: String oldDirectory = this .directory;
154: this .directory = path;
155: this .directoryFile = null;
156: support.firePropertyChange("directory", oldDirectory,
157: this .directory);
158:
159: }
160:
161: /**
162: * Return descriptive information about this Store implementation and
163: * the corresponding version number, in the format
164: * <code><description>/<version></code>.
165: */
166: public String getInfo() {
167:
168: return (info);
169:
170: }
171:
172: /**
173: * Return the thread name for this Store.
174: */
175: public String getThreadName() {
176: return (threadName);
177: }
178:
179: /**
180: * Return the name for this Store, used for logging.
181: */
182: public String getStoreName() {
183: return (storeName);
184: }
185:
186: /**
187: * Return the number of Sessions present in this Store.
188: *
189: * @exception IOException if an input/output error occurs
190: */
191: public int getSize() throws IOException {
192:
193: // Acquire the list of files in our storage directory
194: File file = directory();
195: if (file == null) {
196: return (0);
197: }
198: String files[] = file.list();
199:
200: // Figure out which files are sessions
201: int keycount = 0;
202: for (int i = 0; i < files.length; i++) {
203: if (files[i].endsWith(FILE_EXT)) {
204: keycount++;
205: }
206: }
207: return (keycount);
208:
209: }
210:
211: // --------------------------------------------------------- Public Methods
212:
213: /**
214: * Remove all of the Sessions in this Store.
215: *
216: * @exception IOException if an input/output error occurs
217: */
218: public void clear() throws IOException {
219:
220: String[] keys = keys();
221: for (int i = 0; i < keys.length; i++) {
222: remove(keys[i]);
223: }
224:
225: }
226:
227: /**
228: * Return an array containing the session identifiers of all Sessions
229: * currently saved in this Store. If there are no such Sessions, a
230: * zero-length array is returned.
231: *
232: * @exception IOException if an input/output error occurred
233: */
234: public String[] keys() throws IOException {
235:
236: // Acquire the list of files in our storage directory
237: File file = directory();
238: if (file == null) {
239: return (new String[0]);
240: }
241: String files[] = file.list();
242:
243: // Build and return the list of session identifiers
244: ArrayList list = new ArrayList();
245: int n = FILE_EXT.length();
246: for (int i = 0; i < files.length; i++) {
247: if (files[i].endsWith(FILE_EXT)) {
248: list.add(files[i].substring(0, files[i].length() - n));
249: }
250: }
251: return ((String[]) list.toArray(new String[list.size()]));
252:
253: }
254:
255: /**
256: * Load and return the Session associated with the specified session
257: * identifier from this Store, without removing it. If there is no
258: * such stored Session, return <code>null</code>.
259: *
260: * @param id Session identifier of the session to load
261: *
262: * @exception ClassNotFoundException if a deserialization error occurs
263: * @exception IOException if an input/output error occurs
264: */
265: public Session load(String id) throws ClassNotFoundException,
266: IOException {
267:
268: // Open an input stream to the specified pathname, if any
269: File file = file(id);
270: if (file == null) {
271: return (null);
272: }
273: if (debug >= 1) {
274: log(sm.getString(getStoreName() + ".loading", id, file
275: .getAbsolutePath()));
276: }
277:
278: FileInputStream fis = null;
279: ObjectInputStream ois = null;
280: Loader loader = null;
281: ClassLoader classLoader = null;
282: try {
283: fis = new FileInputStream(file.getAbsolutePath());
284: BufferedInputStream bis = new BufferedInputStream(fis);
285: Container container = manager.getContainer();
286: if (container != null)
287: loader = container.getLoader();
288: if (loader != null)
289: classLoader = loader.getClassLoader();
290: if (classLoader != null)
291: ois = new CustomObjectInputStream(bis, classLoader);
292: else
293: ois = new ObjectInputStream(bis);
294: } catch (FileNotFoundException e) {
295: if (debug >= 1)
296: log("No persisted data file found");
297: return (null);
298: } catch (IOException e) {
299: if (ois != null) {
300: try {
301: ois.close();
302: } catch (IOException f) {
303: ;
304: }
305: ois = null;
306: }
307: throw e;
308: }
309:
310: try {
311: StandardSession session = (StandardSession) manager
312: .createSession();
313: session.readObjectData(ois);
314: session.setManager(manager);
315: return (session);
316: } finally {
317: // Close the input stream
318: if (ois != null) {
319: try {
320: ois.close();
321: } catch (IOException f) {
322: ;
323: }
324: }
325: }
326: }
327:
328: /**
329: * Remove the Session with the specified session identifier from
330: * this Store, if present. If no such Session is present, this method
331: * takes no action.
332: *
333: * @param id Session identifier of the Session to be removed
334: *
335: * @exception IOException if an input/output error occurs
336: */
337: public void remove(String id) throws IOException {
338:
339: File file = file(id);
340: if (file == null) {
341: return;
342: }
343: if (debug >= 1) {
344: log(sm.getString(getStoreName() + ".removing", id, file
345: .getAbsolutePath()));
346: }
347: file.delete();
348:
349: }
350:
351: /**
352: * Save the specified Session into this Store. Any previously saved
353: * information for the associated session identifier is replaced.
354: *
355: * @param session Session to be saved
356: *
357: * @exception IOException if an input/output error occurs
358: */
359: public void save(Session session) throws IOException {
360:
361: // Open an output stream to the specified pathname, if any
362: File file = file(session.getId());
363: if (file == null) {
364: return;
365: }
366: if (debug >= 1) {
367: log(sm.getString(getStoreName() + ".saving", session
368: .getId(), file.getAbsolutePath()));
369: }
370: FileOutputStream fos = null;
371: ObjectOutputStream oos = null;
372: try {
373: fos = new FileOutputStream(file.getAbsolutePath());
374: oos = new ObjectOutputStream(new BufferedOutputStream(fos));
375: } catch (IOException e) {
376: if (oos != null) {
377: try {
378: oos.close();
379: } catch (IOException f) {
380: ;
381: }
382: }
383: throw e;
384: }
385:
386: try {
387: ((StandardSession) session).writeObjectData(oos);
388: } finally {
389: oos.close();
390: }
391:
392: }
393:
394: // -------------------------------------------------------- Private Methods
395:
396: /**
397: * Return a File object representing the pathname to our
398: * session persistence directory, if any. The directory will be
399: * created if it does not already exist.
400: */
401: private File directory() {
402:
403: if (this .directory == null) {
404: return (null);
405: }
406: if (this .directoryFile != null) {
407: // NOTE: Race condition is harmless, so do not synchronize
408: return (this .directoryFile);
409: }
410: File file = new File(this .directory);
411: if (!file.isAbsolute()) {
412: Container container = manager.getContainer();
413: if (container instanceof Context) {
414: ServletContext servletContext = ((Context) container)
415: .getServletContext();
416: File work = (File) servletContext
417: .getAttribute(Globals.WORK_DIR_ATTR);
418: file = new File(work, this .directory);
419: } else {
420: throw new IllegalArgumentException(
421: "Parent Container is not a Context");
422: }
423: }
424: if (!file.exists() || !file.isDirectory()) {
425: file.delete();
426: file.mkdirs();
427: }
428: this .directoryFile = file;
429: return (file);
430:
431: }
432:
433: /**
434: * Return a File object representing the pathname to our
435: * session persistence file, if any.
436: *
437: * @param id The ID of the Session to be retrieved. This is
438: * used in the file naming.
439: */
440: private File file(String id) {
441:
442: if (this .directory == null) {
443: return (null);
444: }
445: String filename = id + FILE_EXT;
446: File file = new File(directory(), filename);
447: return (file);
448:
449: }
450:
451: }
|