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