Source code: com/lutris/util/FilePersistentStore.java
1 /*
2 * Enhydra Java Application Server Project
3 *
4 * The contents of this file are subject to the Enhydra Public License
5 * Version 1.1 (the "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy of the License on
7 * the Enhydra web site ( http://www.enhydra.org/ ).
8 *
9 * Software distributed under the License is distributed on an "AS IS"
10 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11 * the License for the specific terms governing rights and limitations
12 * under the License.
13 *
14 * The Initial Developer of the Enhydra Application Server is Lutris
15 * Technologies, Inc. The Enhydra Application Server and portions created
16 * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17 * All Rights Reserved.
18 *
19 * Contributor(s):
20 *
21 * $Id: FilePersistentStore.java,v 1.10.12.1 2000/10/19 17:58:53 jasona Exp $
22 */
23
24 package com.lutris.util;
25 import java.io.*;
26 import java.util.NoSuchElementException;
27 import java.lang.reflect.Constructor;
28
29 /**
30 * File system implementation of PersistentStore.
31 *
32 * @author Kyle Clark
33 */
34 public class FilePersistentStore implements PersistentStore {
35
36 private final String DEFAULT_DIR_PROPERTY = "user.dir";
37 private final String FILE_SEPARATOR_PROPERTY = "file.separator";
38 private final String SERIAL_SUFFIX = ".srl"; // suffix for serialized objects.
39
40
41 /**
42 * Inner class that implements keys enumeration for this object.
43 */
44 private class FilePersistentStoreKeys implements java.util.Enumeration {
45
46 private String [] keys;
47 private int idx = 0;
48
49 /**
50 * Constructor.
51 */
52 FilePersistentStoreKeys() {
53 keys = new String[0];
54 }
55
56 /**
57 * Constructor.
58 *
59 * @param keys
60 * The keys.
61 */
62 FilePersistentStoreKeys(String [] keys) {
63 this.keys = keys;
64 }
65
66
67 /**
68 * Tests if this enumeration contains more keys.
69 *
70 * @return
71 * True if it does contain more elements, false otherwise.
72 */
73 public boolean hasMoreElements() {
74 return (idx < keys.length);
75 }
76
77 /**
78 * Returns the next key of this enumeration.
79 *
80 * @return
81 * The next key.
82 * @exception java.util.NoSuchElementException
83 * if there are no more keys in this enumeration.
84 */
85 public Object nextElement() throws NoSuchElementException {
86 if (!hasMoreElements())
87 throw new java.util.NoSuchElementException("no more elements");
88
89 String k = keys[idx];
90 idx++;
91 return k;
92 }
93 }
94
95 /**
96 * Inner class that implements a filename filter.
97 */
98 private class FileStoreFilter implements FilenameFilter {
99 private String suffix = "";
100 FileStoreFilter(String suffix) {
101 this.suffix = suffix;
102 }
103 public boolean accept(File dir, String name) {
104 return name.endsWith(suffix);
105 }
106 }
107
108
109 /**
110 * Path separator used by operating system.
111 */
112 private String fileSeparator;
113
114 /**
115 * Directory (pathname) where objects are to be
116 * stored/retrieved. The default is the
117 * current directory.
118 */
119 private String storeDirectory;
120
121 /**
122 * The class loader used when reading in serialized data.
123 */
124 private Constructor objectInputStreamConstructor;
125
126 /**
127 * The loader that should be used to load serialized data.
128 */
129 private ClassLoader loader;
130
131 /**
132 * Public constructor. Sets the <code>storeDirectory</code>,
133 * the directory (repository) where objects
134 * are stored/retreived to <code>/tmp</code>.
135 *
136 * @exception java.io.IOException
137 * If <code>/tmp</code>
138 * already exists but is not a directory. Or if
139 * <code>/tmp</code> does not exist and could
140 * not be created.
141 * @exception java.lang.SecurityException
142 * If a security
143 * manager is running but <code>/tmp</code>
144 * could not be created due to inappropriate
145 * permissions.
146 */
147 public FilePersistentStore()
148 throws PersistentStoreException {
149 init(null, null);
150 }
151
152 /**
153 * Public constructor. Sets the <code>storeDirectory</code>,
154 * the directory (repository) where objects
155 * are stored/retreived to <code>/tmp</code>.
156 *
157 * @param loader the class loader to use when reading
158 * in serialized data.
159 * @exception java.io.IOException
160 * If <code>/tmp</code>
161 * already exists but is not a directory. Or if
162 * <code>/tmp</code> does not exist and could
163 * not be created.
164 * @exception java.lang.SecurityException
165 * If a security
166 * manager is running but <code>/tmp</code>
167 * could not be created due to inappropriate
168 * permissions.
169 */
170 public FilePersistentStore(ClassLoader loader)
171 throws IOException, SecurityException, PersistentStoreException {
172 init(null, loader);
173 }
174
175 /**
176 * Public constructor that allows one to specify
177 * the directory (repository)
178 * where objects are stored/retrieved.
179 * If the specified directory doesn't exist then it is created.
180 *
181 * @param storeDirectory
182 * The repository (directory) for storing and retrieving objects.
183 * @exception java.io.IOException
184 * If <code>storeDirectory</code>
185 * already exists but is not a directory. Or if the
186 * <code>storeDirectory</code> does not exist and could
187 * not be created.
188 * @exception java.lang.SecurityException
189 * If a security
190 * manager is running but <code>storeDirectory</code>
191 * could not be created due to inappropriate
192 * permissions.
193 */
194 public FilePersistentStore(String storeDirectory)
195 throws PersistentStoreException {
196 init(storeDirectory, null);
197 }
198
199 /**
200 * Public constructor that allows one to specify
201 * the directory (repository)
202 * where objects are stored/retrieved.
203 * If the specified directory doesn't exist then it is created.
204 *
205 * @param storeDirectory
206 * The repository (directory) for storing and retrieving objects.
207 * @exception java.io.IOException
208 * If <code>storeDirectory</code>
209 * already exists but is not a directory. Or if the
210 * <code>storeDirectory</code> does not exist and could
211 * not be created.
212 * @exception java.lang.SecurityException
213 * If a security
214 * manager is running but <code>storeDirectory</code>
215 * could not be created due to inappropriate
216 * permissions.
217 */
218 public FilePersistentStore(String storeDirectory, ClassLoader loader)
219 throws PersistentStoreException {
220 init(storeDirectory, loader);
221 }
222
223 /**
224 * Gets a reference to the object input stream constructor.
225 *
226 * @param storeDirectory
227 * The repository (directory) for storing and retrieving objects.
228 * @exception java.io.IOException
229 * If <code>storeDirectory</code>
230 * already exists but is not a directory. Or if the
231 * <code>storeDirectory</code> does not exist and could
232 * not be created.
233 * @exception java.lang.SecurityException
234 * If a security
235 * manager is running but <code>storeDirectory</code>
236 * could not be created due to inappropriate
237 * permissions.
238 */
239 private void init(String storeDirectory, ClassLoader loader)
240 throws PersistentStoreException {
241 try {
242 this.loader = loader;
243 fileSeparator = System.getProperty(FILE_SEPARATOR_PROPERTY);
244 if (storeDirectory == null) {
245 setStoreDirectory(fileSeparator + "tmp");
246 } else {
247 setStoreDirectory(storeDirectory);
248 }
249 } catch (Exception e) {
250 throw new PersistentStoreException(e);
251 }
252 }
253
254 /**
255 * Sets the location (directory) from where objects are
256 * retrieved/archived. If the specified directory doesn't
257 * exist then it is created.
258 *
259 * @param storeDirectory The repository (directory) for
260 * storing and retrieving objects.
261 * @exception java.io.IOException If <code>storeDirectory</code>
262 * already exists but is not a directory. Or if the
263 * <code>storeDirectory</code> does not exist and could
264 * not be created.
265 * @exception java.lang.SecurityException If a security
266 * manager is running but <code>storeDirectory</code>
267 * could not be created because due to inappropriate
268 * permissions.
269 */
270 private void setStoreDirectory(String storeDirectory)
271 throws IOException, SecurityException {
272 File f = new File(storeDirectory);
273 if (f.exists() && !f.isDirectory())
274 throw new IOException(storeDirectory +
275 " already exists but is not a directory.");
276 if (!f.exists() && !f.mkdirs())
277 throw new IOException("Unable to create directory " +
278 storeDirectory);
279 this.storeDirectory = storeDirectory;
280 }
281
282
283 /**
284 * Returns the location where objects are
285 * stored/retrieved.
286 *
287 * @return The directory (repository) where
288 * objects are stored/retrieved.
289 */
290 public String getStoreDirectory() {
291 return this.storeDirectory;
292 }
293
294 /**
295 * Method that calculates the absolute path to the
296 * stored object.
297 *
298 * @param key The user for whom the archive name
299 * is calculated.
300 * @return The filename associated w/ key.
301 */
302 private String filename(String key) {
303 return storeDirectory + fileSeparator + convertKeyToHex(key) + SERIAL_SUFFIX;
304 }
305
306 /**
307 * Hexadecimal characters corresponding to each half byte value.
308 */
309 private static final char[] HexChars = {
310 '0', '1', '2', '3', '4', '5', '6', '7',
311 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
312 };
313
314
315 /**
316 * Converts an arbitrary string to ASCII hexadecimal string
317 * form, with two hex characters corresponding to each byte. The
318 * length of the resultant string in characters will be twice the
319 * length of the original string.
320 *
321 * @param key
322 * The string to convert to ASCII hex form.
323 * @return
324 * An ASCII hexadecimal numeric string representing the
325 * original string.
326 */
327 private String convertKeyToHex(String key) {
328 byte [] bytes = key.getBytes();
329 StringBuffer sb = new StringBuffer();
330 int i;
331 for (i=0; i < bytes.length; i++) {
332 sb.append(HexChars[(bytes[i] >> 4) & 0xf]);
333 sb.append(HexChars[bytes[i] & 0xf]);
334 }
335 return new String(sb);
336 }
337
338 /**
339 * Converts an arbitrary ASCII hexadecimal string,
340 * with two hex characters corresponding to each byte, into
341 * a string.
342 *
343 * @param hex
344 * The hexadecimal encoded string to convert.
345 * @return
346 * The original string.
347 */
348 private String convertHexToKey(String hex) {
349 char [] chars = new char[hex.length()];
350 hex.getChars(0, hex.length(), chars, 0);
351 StringBuffer sb = new StringBuffer();
352 for (int i=0; i<chars.length/2; i++) {
353 int idx = 2*i;
354 sb.append((char)((hexCharToByte(chars[idx])<<4) +
355 (hexCharToByte(chars[idx+1]))));
356 }
357 return new String(sb);
358 }
359
360 /**
361 * Method that maps a hexadecimal character to a byte value.
362 *
363 * @param c
364 * The character to map.
365 */
366 private byte hexCharToByte(char c) {
367 switch (c) {
368 case '1':
369 return (byte)1;
370 case '2':
371 return (byte)2;
372 case '3':
373 return (byte)3;
374 case '4':
375 return (byte)4;
376 case '5':
377 return (byte)5;
378 case '6':
379 return (byte)6;
380 case '7':
381 return (byte)7;
382 case '8':
383 return (byte)8;
384 case '9':
385 return (byte)9;
386 case 'a':
387 case 'A':
388 return (byte)10;
389 case 'b':
390 case 'B':
391 return (byte)11;
392 case 'c':
393 case 'C':
394 return (byte)12;
395 case 'd':
396 case 'D':
397 return (byte)13;
398 case 'e':
399 case 'E':
400 return (byte)14;
401 case 'f':
402 case 'F':
403 return (byte)15;
404 case '0':
405 default:
406 return (byte)0;
407 }
408 }
409
410 /**
411 * Method to store and object (persistent).
412 *
413 * @param key
414 * The key by which to identify the stored object.
415 * The key name has some system constraints. Its length
416 * must not exceed half the maximum file name length on
417 * the system. If it does, an exception is thrown.
418 * @param obj
419 * The serializable object to store.
420 * @exception PersistentStoreException
421 * if the object cannot cannot be stored.
422 */
423 public void store(String key, Serializable obj)
424 throws PersistentStoreException {
425 if (exists(key))
426 throw new
427 PersistentStoreException("An object is already stored as " +
428 key);
429 FileOutputStream out = null;
430 try {
431 out = new FileOutputStream(filename(key));
432 ObjectOutputStream objOut = new ObjectOutputStream(out);
433 objOut.writeObject(obj);
434 objOut.flush();
435 out.close();
436 }
437 catch (Exception ex) {
438 if (out != null) {
439 try { out.close(); } catch (Exception e) {}
440 }
441 delete(key);
442 throw new PersistentStoreException(ex);
443 }
444 }
445
446
447 /**
448 * Method to retrieve a stored object.
449 *
450 * @param key
451 * The key of the object that is to be retreived.
452 * @return
453 * The stored object. If an object is not stored under key,
454 * then <code>null</code> is returned.
455 * @see #remove
456 * @exception PersistentStoreException
457 * if the object could not be retrieved.
458 */
459 public Object retrieve(String key) throws PersistentStoreException {
460 if (!exists(key)) {
461 return null;
462 }
463 try {
464 FileInputStream in = new FileInputStream(filename(key));
465 LoaderObjectInputStream objIn = new LoaderObjectInputStream(in, loader);
466 Object obj = objIn.readObject();
467 objIn.close();
468 return obj;
469 }
470 catch (Exception ex) {
471 throw new PersistentStoreException(ex);
472 }
473 }
474
475
476 /**
477 * Method to query if an an object is stored.
478 *
479 * @param key
480 * The key by which to identify the stored object.
481 * @return
482 * True if an object is stored under key.
483 * @exception PersistentStoreException
484 * If The exsitence of object could not be determined.
485 */
486 public boolean exists(String key)
487 throws PersistentStoreException {
488 File f = new File(filename(key));
489 return f.exists();
490 }
491
492
493 /**
494 * Method to simultaneously retrieve and remove an
495 * object from persistent store. If an object is not
496 * stored under key, then null is returned.
497 *
498 * @param key
499 * The key by which to identify the stored object
500 * that is to be removed.
501 * @return
502 * The object that has been removed.
503 * @exception PersistentStoreException
504 * If the object could not be retrieved before being deleted.
505 */
506 public Object remove(String key) throws PersistentStoreException {
507 Object obj = retrieve(key);
508 delete(key);
509 return obj;
510 }
511
512
513 /**
514 * Method to delete a a key. Any objects stored under
515 * key are also removed. If key is not defined, then
516 * this method does nothing.
517 *
518 * @param key
519 * The key to remove.
520 */
521 public void delete(String key) {
522 File f = new File(filename(key));
523 if (f.exists()) {
524 f.delete();
525 }
526 }
527
528
529 /**
530 * Method that returns an enumration of the keys
531 * of this persistent store.
532 *
533 * @exception com.lutris.util.PersistentStoreException
534 * if the enumeration could not be determined.
535 */
536 public java.util.Enumeration keys() throws PersistentStoreException {
537 File dir = new File(storeDirectory);
538 FileStoreFilter filter = new FileStoreFilter(SERIAL_SUFFIX);
539 String [] hexKeys = dir.list(filter);
540 String [] keys = new String [hexKeys.length];
541 for (int idx=0; idx<hexKeys.length; idx++) {
542 // Strip off the suffix
543 int suffixIdx = hexKeys[idx].lastIndexOf(SERIAL_SUFFIX);
544 if (suffixIdx >= 0)
545 hexKeys[idx] = hexKeys[idx].substring(0, suffixIdx);
546 // Get the real key name
547 keys[idx] = convertHexToKey(hexKeys[idx]);
548 }
549
550 return new FilePersistentStoreKeys(keys);
551 }
552
553 }
554
555 // Internal class that can load objects with the load we specify.
556 class LoaderObjectInputStream extends ObjectInputStream {
557
558 private ClassLoader loader;
559
560 public LoaderObjectInputStream(InputStream in, ClassLoader loader)
561 throws IOException, StreamCorruptedException {
562 super(in);
563 this.loader = loader;
564 }
565
566 /**
567 * Subclasses may implement this method to allow classes to be
568 * fetched from an alternate source.
569 *
570 * The corresponding method in ObjectOutputStream is
571 * annotateClass. This method will be invoked only once for each
572 * unique class in the stream. This method can be implemented by
573 * subclasses to use an alternate loading mechanism but must
574 * return a Class object. Once returned, the serialVersionUID of the
575 * class is compared to the serialVersionUID of the serialized class.
576 * If there is a mismatch, the deserialization fails and an exception
577 * is raised. <p>
578 *
579 * By default the class name is resolved relative to the class
580 * that called readObject. <p>
581 *
582 * @exception ClassNotFoundException If class of
583 * a serialized object cannot be found.
584 * @since JDK1.1
585 */
586 protected Class resolveClass(ObjectStreamClass v)
587 throws IOException, ClassNotFoundException {
588 if (loader != null) {
589 return loader.loadClass(v.getName());
590 } else {
591 return super.resolveClass(v);
592 }
593 }
594
595 }