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.io.BufferedInputStream;
020: import java.io.BufferedOutputStream;
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.FileNotFoundException;
024: import java.io.FileOutputStream;
025: import java.io.IOException;
026: import java.io.ObjectInputStream;
027: import java.io.ObjectOutputStream;
028: import java.util.ArrayList;
029:
030: import javax.servlet.ServletContext;
031:
032: import org.apache.catalina.Container;
033: import org.apache.catalina.Context;
034: import org.apache.catalina.Globals;
035: import org.apache.catalina.Loader;
036: import org.apache.catalina.Session;
037: import org.apache.catalina.Store;
038: import org.apache.catalina.util.CustomObjectInputStream;
039:
040: /**
041: * Concrete implementation of the <b>Store</b> interface that utilizes
042: * a file per saved Session in a configured directory. Sessions that are
043: * saved are still subject to being expired based on inactivity.
044: *
045: * @author Craig R. McClanahan
046: * @version $Revision: 1.4 $ $Date: 2004/02/27 14:58:46 $
047: */
048:
049: public final class FileStore extends StoreBase implements Store {
050:
051: // ----------------------------------------------------- Constants
052:
053: /**
054: * The extension to use for serialized session filenames.
055: */
056: private static final String FILE_EXT = ".session";
057:
058: // ----------------------------------------------------- Instance Variables
059:
060: /**
061: * The pathname of the directory in which Sessions are stored.
062: * This may be an absolute pathname, or a relative path that is
063: * resolved against the temporary work directory for this application.
064: */
065: private String directory = ".";
066:
067: /**
068: * A File representing the directory in which Sessions are stored.
069: */
070: private File directoryFile = null;
071:
072: /**
073: * The descriptive information about this implementation.
074: */
075: private static final String info = "FileStore/1.0";
076:
077: /**
078: * Name to register for this Store, used for logging.
079: */
080: private static final String storeName = "fileStore";
081:
082: /**
083: * Name to register for the background thread.
084: */
085: private static final String threadName = "FileStore";
086:
087: // ------------------------------------------------------------- Properties
088:
089: /**
090: * Return the directory path for this Store.
091: */
092: public String getDirectory() {
093:
094: return (directory);
095:
096: }
097:
098: /**
099: * Set the directory path for this Store.
100: *
101: * @param path The new directory path
102: */
103: public void setDirectory(String path) {
104:
105: String oldDirectory = this .directory;
106: this .directory = path;
107: this .directoryFile = null;
108: support.firePropertyChange("directory", oldDirectory,
109: this .directory);
110:
111: }
112:
113: /**
114: * Return descriptive information about this Store implementation and
115: * the corresponding version number, in the format
116: * <code><description>/<version></code>.
117: */
118: public String getInfo() {
119:
120: return (info);
121:
122: }
123:
124: /**
125: * Return the thread name for this Store.
126: */
127: public String getThreadName() {
128: return (threadName);
129: }
130:
131: /**
132: * Return the name for this Store, used for logging.
133: */
134: public String getStoreName() {
135: return (storeName);
136: }
137:
138: /**
139: * Return the number of Sessions present in this Store.
140: *
141: * @exception IOException if an input/output error occurs
142: */
143: public int getSize() throws IOException {
144:
145: // Acquire the list of files in our storage directory
146: File file = directory();
147: if (file == null) {
148: return (0);
149: }
150: String files[] = file.list();
151:
152: // Figure out which files are sessions
153: int keycount = 0;
154: for (int i = 0; i < files.length; i++) {
155: if (files[i].endsWith(FILE_EXT)) {
156: keycount++;
157: }
158: }
159: return (keycount);
160:
161: }
162:
163: // --------------------------------------------------------- Public Methods
164:
165: /**
166: * Remove all of the Sessions in this Store.
167: *
168: * @exception IOException if an input/output error occurs
169: */
170: public void clear() throws IOException {
171:
172: String[] keys = keys();
173: for (int i = 0; i < keys.length; i++) {
174: remove(keys[i]);
175: }
176:
177: }
178:
179: /**
180: * Return an array containing the session identifiers of all Sessions
181: * currently saved in this Store. If there are no such Sessions, a
182: * zero-length array is returned.
183: *
184: * @exception IOException if an input/output error occurred
185: */
186: public String[] keys() throws IOException {
187:
188: // Acquire the list of files in our storage directory
189: File file = directory();
190: if (file == null) {
191: return (new String[0]);
192: }
193: String files[] = file.list();
194:
195: // Build and return the list of session identifiers
196: ArrayList list = new ArrayList();
197: int n = FILE_EXT.length();
198: for (int i = 0; i < files.length; i++) {
199: if (files[i].endsWith(FILE_EXT)) {
200: list.add(files[i].substring(0, files[i].length() - n));
201: }
202: }
203: return ((String[]) list.toArray(new String[list.size()]));
204:
205: }
206:
207: /**
208: * Load and return the Session associated with the specified session
209: * identifier from this Store, without removing it. If there is no
210: * such stored Session, return <code>null</code>.
211: *
212: * @param id Session identifier of the session to load
213: *
214: * @exception ClassNotFoundException if a deserialization error occurs
215: * @exception IOException if an input/output error occurs
216: */
217: public Session load(String id) throws ClassNotFoundException,
218: IOException {
219:
220: // Open an input stream to the specified pathname, if any
221: File file = file(id);
222: if (file == null) {
223: return (null);
224: }
225:
226: if (!file.exists()) {
227: return (null);
228: }
229: if (debug >= 1) {
230: log(sm.getString(getStoreName() + ".loading", id, file
231: .getAbsolutePath()));
232: }
233:
234: FileInputStream fis = null;
235: ObjectInputStream ois = null;
236: Loader loader = null;
237: ClassLoader classLoader = null;
238: try {
239: fis = new FileInputStream(file.getAbsolutePath());
240: BufferedInputStream bis = new BufferedInputStream(fis);
241: Container container = manager.getContainer();
242: if (container != null)
243: loader = container.getLoader();
244: if (loader != null)
245: classLoader = loader.getClassLoader();
246: if (classLoader != null)
247: ois = new CustomObjectInputStream(bis, classLoader);
248: else
249: ois = new ObjectInputStream(bis);
250: } catch (FileNotFoundException e) {
251: if (debug >= 1)
252: log("No persisted data file found");
253: return (null);
254: } catch (IOException e) {
255: if (ois != null) {
256: try {
257: ois.close();
258: } catch (IOException f) {
259: ;
260: }
261: ois = null;
262: }
263: throw e;
264: }
265:
266: try {
267: StandardSession session = (StandardSession) manager
268: .createEmptySession();
269: session.readObjectData(ois);
270: session.setManager(manager);
271: return (session);
272: } finally {
273: // Close the input stream
274: if (ois != null) {
275: try {
276: ois.close();
277: } catch (IOException f) {
278: ;
279: }
280: }
281: }
282: }
283:
284: /**
285: * Remove the Session with the specified session identifier from
286: * this Store, if present. If no such Session is present, this method
287: * takes no action.
288: *
289: * @param id Session identifier of the Session to be removed
290: *
291: * @exception IOException if an input/output error occurs
292: */
293: public void remove(String id) throws IOException {
294:
295: File file = file(id);
296: if (file == null) {
297: return;
298: }
299: if (debug >= 1) {
300: log(sm.getString(getStoreName() + ".removing", id, file
301: .getAbsolutePath()));
302: }
303: file.delete();
304:
305: }
306:
307: /**
308: * Save the specified Session into this Store. Any previously saved
309: * information for the associated session identifier is replaced.
310: *
311: * @param session Session to be saved
312: *
313: * @exception IOException if an input/output error occurs
314: */
315: public void save(Session session) throws IOException {
316:
317: // Open an output stream to the specified pathname, if any
318: File file = file(session.getId());
319: if (file == null) {
320: return;
321: }
322: if (debug >= 1) {
323: log(sm.getString(getStoreName() + ".saving", session
324: .getId(), file.getAbsolutePath()));
325: }
326: FileOutputStream fos = null;
327: ObjectOutputStream oos = null;
328: try {
329: fos = new FileOutputStream(file.getAbsolutePath());
330: oos = new ObjectOutputStream(new BufferedOutputStream(fos));
331: } catch (IOException e) {
332: if (oos != null) {
333: try {
334: oos.close();
335: } catch (IOException f) {
336: ;
337: }
338: }
339: throw e;
340: }
341:
342: try {
343: ((StandardSession) session).writeObjectData(oos);
344: } finally {
345: oos.close();
346: }
347:
348: }
349:
350: // -------------------------------------------------------- Private Methods
351:
352: /**
353: * Return a File object representing the pathname to our
354: * session persistence directory, if any. The directory will be
355: * created if it does not already exist.
356: */
357: private File directory() {
358:
359: if (this .directory == null) {
360: return (null);
361: }
362: if (this .directoryFile != null) {
363: // NOTE: Race condition is harmless, so do not synchronize
364: return (this .directoryFile);
365: }
366: File file = new File(this .directory);
367: if (!file.isAbsolute()) {
368: Container container = manager.getContainer();
369: if (container instanceof Context) {
370: ServletContext servletContext = ((Context) container)
371: .getServletContext();
372: File work = (File) servletContext
373: .getAttribute(Globals.WORK_DIR_ATTR);
374: file = new File(work, this .directory);
375: } else {
376: throw new IllegalArgumentException(
377: "Parent Container is not a Context");
378: }
379: }
380: if (!file.exists() || !file.isDirectory()) {
381: file.delete();
382: file.mkdirs();
383: }
384: this .directoryFile = file;
385: return (file);
386:
387: }
388:
389: /**
390: * Return a File object representing the pathname to our
391: * session persistence file, if any.
392: *
393: * @param id The ID of the Session to be retrieved. This is
394: * used in the file naming.
395: */
396: private File file(String id) {
397:
398: if (this .directory == null) {
399: return (null);
400: }
401: String filename = id + FILE_EXT;
402: File file = new File(directory(), filename);
403: return (file);
404:
405: }
406:
407: }
|