1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.session;
20
21
22 import java.io.BufferedInputStream;
23 import java.io.BufferedOutputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileNotFoundException;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.ObjectInputStream;
30 import java.io.ObjectOutputStream;
31 import java.util.ArrayList;
32
33 import javax.servlet.ServletContext;
34
35 import org.apache.catalina.Container;
36 import org.apache.catalina.Context;
37 import org.apache.catalina.Globals;
38 import org.apache.catalina.Loader;
39 import org.apache.catalina.Session;
40 import org.apache.catalina.Store;
41 import org.apache.catalina.util.CustomObjectInputStream;
42
43
44 /**
45 * Concrete implementation of the <b>Store</b> interface that utilizes
46 * a file per saved Session in a configured directory. Sessions that are
47 * saved are still subject to being expired based on inactivity.
48 *
49 * @author Craig R. McClanahan
50 * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
51 */
52
53 public final class FileStore
54 extends StoreBase implements Store {
55
56
57 // ----------------------------------------------------- Constants
58
59
60 /**
61 * The extension to use for serialized session filenames.
62 */
63 private static final String FILE_EXT = ".session";
64
65
66 // ----------------------------------------------------- Instance Variables
67
68
69 /**
70 * The pathname of the directory in which Sessions are stored.
71 * This may be an absolute pathname, or a relative path that is
72 * resolved against the temporary work directory for this application.
73 */
74 private String directory = ".";
75
76
77 /**
78 * A File representing the directory in which Sessions are stored.
79 */
80 private File directoryFile = null;
81
82
83 /**
84 * The descriptive information about this implementation.
85 */
86 private static final String info = "FileStore/1.0";
87
88 /**
89 * Name to register for this Store, used for logging.
90 */
91 private static final String storeName = "fileStore";
92
93 /**
94 * Name to register for the background thread.
95 */
96 private static final String threadName = "FileStore";
97
98
99 // ------------------------------------------------------------- Properties
100
101
102 /**
103 * Return the directory path for this Store.
104 */
105 public String getDirectory() {
106
107 return (directory);
108
109 }
110
111
112 /**
113 * Set the directory path for this Store.
114 *
115 * @param path The new directory path
116 */
117 public void setDirectory(String path) {
118
119 String oldDirectory = this.directory;
120 this.directory = path;
121 this.directoryFile = null;
122 support.firePropertyChange("directory", oldDirectory,
123 this.directory);
124
125 }
126
127
128 /**
129 * Return descriptive information about this Store implementation and
130 * the corresponding version number, in the format
131 * <code><description>/<version></code>.
132 */
133 public String getInfo() {
134
135 return (info);
136
137 }
138
139 /**
140 * Return the thread name for this Store.
141 */
142 public String getThreadName() {
143 return(threadName);
144 }
145
146 /**
147 * Return the name for this Store, used for logging.
148 */
149 public String getStoreName() {
150 return(storeName);
151 }
152
153
154 /**
155 * Return the number of Sessions present in this Store.
156 *
157 * @exception IOException if an input/output error occurs
158 */
159 public int getSize() throws IOException {
160
161 // Acquire the list of files in our storage directory
162 File file = directory();
163 if (file == null) {
164 return (0);
165 }
166 String files[] = file.list();
167
168 // Figure out which files are sessions
169 int keycount = 0;
170 for (int i = 0; i < files.length; i++) {
171 if (files[i].endsWith(FILE_EXT)) {
172 keycount++;
173 }
174 }
175 return (keycount);
176
177 }
178
179
180 // --------------------------------------------------------- Public Methods
181
182
183 /**
184 * Remove all of the Sessions in this Store.
185 *
186 * @exception IOException if an input/output error occurs
187 */
188 public void clear()
189 throws IOException {
190
191 String[] keys = keys();
192 for (int i = 0; i < keys.length; i++) {
193 remove(keys[i]);
194 }
195
196 }
197
198
199 /**
200 * Return an array containing the session identifiers of all Sessions
201 * currently saved in this Store. If there are no such Sessions, a
202 * zero-length array is returned.
203 *
204 * @exception IOException if an input/output error occurred
205 */
206 public String[] keys() throws IOException {
207
208 // Acquire the list of files in our storage directory
209 File file = directory();
210 if (file == null) {
211 return (new String[0]);
212 }
213
214 String files[] = file.list();
215
216 // Bugzilla 32130
217 if((files == null) || (files.length < 1)) {
218 return (new String[0]);
219 }
220
221 // Build and return the list of session identifiers
222 ArrayList list = new ArrayList();
223 int n = FILE_EXT.length();
224 for (int i = 0; i < files.length; i++) {
225 if (files[i].endsWith(FILE_EXT)) {
226 list.add(files[i].substring(0, files[i].length() - n));
227 }
228 }
229 return ((String[]) list.toArray(new String[list.size()]));
230
231 }
232
233
234 /**
235 * Load and return the Session associated with the specified session
236 * identifier from this Store, without removing it. If there is no
237 * such stored Session, return <code>null</code>.
238 *
239 * @param id Session identifier of the session to load
240 *
241 * @exception ClassNotFoundException if a deserialization error occurs
242 * @exception IOException if an input/output error occurs
243 */
244 public Session load(String id)
245 throws ClassNotFoundException, IOException {
246
247 // Open an input stream to the specified pathname, if any
248 File file = file(id);
249 if (file == null) {
250 return (null);
251 }
252
253 if (! file.exists()) {
254 return (null);
255 }
256 if (manager.getContainer().getLogger().isDebugEnabled()) {
257 manager.getContainer().getLogger().debug(sm.getString(getStoreName()+".loading",
258 id, file.getAbsolutePath()));
259 }
260
261 FileInputStream fis = null;
262 ObjectInputStream ois = null;
263 Loader loader = null;
264 ClassLoader classLoader = null;
265 try {
266 fis = new FileInputStream(file.getAbsolutePath());
267 BufferedInputStream bis = new BufferedInputStream(fis);
268 Container container = manager.getContainer();
269 if (container != null)
270 loader = container.getLoader();
271 if (loader != null)
272 classLoader = loader.getClassLoader();
273 if (classLoader != null)
274 ois = new CustomObjectInputStream(bis, classLoader);
275 else
276 ois = new ObjectInputStream(bis);
277 } catch (FileNotFoundException e) {
278 if (manager.getContainer().getLogger().isDebugEnabled())
279 manager.getContainer().getLogger().debug("No persisted data file found");
280 return (null);
281 } catch (IOException e) {
282 if (ois != null) {
283 try {
284 ois.close();
285 } catch (IOException f) {
286 ;
287 }
288 ois = null;
289 }
290 throw e;
291 }
292
293 try {
294 StandardSession session =
295 (StandardSession) manager.createEmptySession();
296 session.readObjectData(ois);
297 session.setManager(manager);
298 return (session);
299 } finally {
300 // Close the input stream
301 if (ois != null) {
302 try {
303 ois.close();
304 } catch (IOException f) {
305 ;
306 }
307 }
308 }
309 }
310
311
312 /**
313 * Remove the Session with the specified session identifier from
314 * this Store, if present. If no such Session is present, this method
315 * takes no action.
316 *
317 * @param id Session identifier of the Session to be removed
318 *
319 * @exception IOException if an input/output error occurs
320 */
321 public void remove(String id) throws IOException {
322
323 File file = file(id);
324 if (file == null) {
325 return;
326 }
327 if (manager.getContainer().getLogger().isDebugEnabled()) {
328 manager.getContainer().getLogger().debug(sm.getString(getStoreName()+".removing",
329 id, file.getAbsolutePath()));
330 }
331 file.delete();
332
333 }
334
335
336 /**
337 * Save the specified Session into this Store. Any previously saved
338 * information for the associated session identifier is replaced.
339 *
340 * @param session Session to be saved
341 *
342 * @exception IOException if an input/output error occurs
343 */
344 public void save(Session session) throws IOException {
345
346 // Open an output stream to the specified pathname, if any
347 File file = file(session.getIdInternal());
348 if (file == null) {
349 return;
350 }
351 if (manager.getContainer().getLogger().isDebugEnabled()) {
352 manager.getContainer().getLogger().debug(sm.getString(getStoreName()+".saving",
353 session.getIdInternal(), file.getAbsolutePath()));
354 }
355 FileOutputStream fos = null;
356 ObjectOutputStream oos = null;
357 try {
358 fos = new FileOutputStream(file.getAbsolutePath());
359 oos = new ObjectOutputStream(new BufferedOutputStream(fos));
360 } catch (IOException e) {
361 if (oos != null) {
362 try {
363 oos.close();
364 } catch (IOException f) {
365 ;
366 }
367 }
368 throw e;
369 }
370
371 try {
372 ((StandardSession)session).writeObjectData(oos);
373 } finally {
374 oos.close();
375 }
376
377 }
378
379
380 // -------------------------------------------------------- Private Methods
381
382
383 /**
384 * Return a File object representing the pathname to our
385 * session persistence directory, if any. The directory will be
386 * created if it does not already exist.
387 */
388 private File directory() {
389
390 if (this.directory == null) {
391 return (null);
392 }
393 if (this.directoryFile != null) {
394 // NOTE: Race condition is harmless, so do not synchronize
395 return (this.directoryFile);
396 }
397 File file = new File(this.directory);
398 if (!file.isAbsolute()) {
399 Container container = manager.getContainer();
400 if (container instanceof Context) {
401 ServletContext servletContext =
402 ((Context) container).getServletContext();
403 File work = (File)
404 servletContext.getAttribute(Globals.WORK_DIR_ATTR);
405 file = new File(work, this.directory);
406 } else {
407 throw new IllegalArgumentException
408 ("Parent Container is not a Context");
409 }
410 }
411 if (!file.exists() || !file.isDirectory()) {
412 file.delete();
413 file.mkdirs();
414 }
415 this.directoryFile = file;
416 return (file);
417
418 }
419
420
421 /**
422 * Return a File object representing the pathname to our
423 * session persistence file, if any.
424 *
425 * @param id The ID of the Session to be retrieved. This is
426 * used in the file naming.
427 */
428 private File file(String id) {
429
430 if (this.directory == null) {
431 return (null);
432 }
433 String filename = id + FILE_EXT;
434 File file = new File(directory(), filename);
435 return (file);
436
437 }
438
439
440 }