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