KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > excalibur > store > impl > AbstractFilesystemStore


1 /*
2  * Copyright 2002-2004 The Apache Software Foundation
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12  * implied.
13  *
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.excalibur.store.impl;
18
19 import java.io.*;
20 import java.util.BitSet;
21 import java.util.Enumeration;
22
23 import EDU.oswego.cs.dl.util.concurrent.Sync;
24
25 import org.apache.avalon.framework.thread.ThreadSafe;
26 import org.apache.excalibur.store.Store;
27
28 /**
29  * Stores objects on the filesystem: String objects as text files,
30  * all other objects are serialized. This class must be subclassed
31  * in order to set the directory the store should work on.
32  *
33  * @author <a HREF="mailto:dev@avalon.apache.org">Avalon Development Team</a>
34  * @version CVS $Id: AbstractFilesystemStore.java,v 1.4 2004/02/28 11:47:31 cziegeler Exp $
35  */

36 public abstract class AbstractFilesystemStore
37 extends AbstractReadWriteStore
38 implements Store, ThreadSafe {
39
40     /** The directory repository */
41     protected File m_directoryFile;
42     protected volatile String m_directoryPath;
43
44     /**
45      * Sets the repository's location
46      */

47     public void setDirectory(final String directory)
48     throws IOException
49     {
50         this.setDirectory(new File(directory));
51     }
52
53     /**
54      * Sets the repository's location
55      */

56     public void setDirectory(final File directory)
57     throws IOException
58     {
59         this.m_directoryFile = directory;
60
61         /* Save directory path prefix */
62         this.m_directoryPath = this.getFullFilename(this.m_directoryFile);
63         this.m_directoryPath += File.separator;
64
65         /* Does directory exist? */
66         if (!this.m_directoryFile.exists())
67         {
68             /* Create it anew */
69             if (!this.m_directoryFile.mkdir())
70             {
71                 throw new IOException(
72                 "Error creating store directory '" + this.m_directoryPath + "': ");
73             }
74         }
75
76         /* Is given file actually a directory? */
77         if (!this.m_directoryFile.isDirectory())
78         {
79             throw new IOException("'" + this.m_directoryPath + "' is not a directory");
80         }
81
82         /* Is directory readable and writable? */
83         if (!(this.m_directoryFile.canRead() && this.m_directoryFile.canWrite()))
84         {
85             throw new IOException(
86                 "Directory '" + this.m_directoryPath + "' is not readable/writable"
87             );
88         }
89     }
90
91     /**
92      * Returns the repository's full pathname
93      */

94     public String getDirectoryPath()
95     {
96         return this.m_directoryPath;
97     }
98
99     /**
100      * Get the File object associated with the given unique key name.
101      */

102     protected Object doGet(final Object key)
103     {
104         final File file = fileFromKey(key);
105
106         if (file != null && file.exists())
107         {
108             if (getLogger().isDebugEnabled())
109             {
110                 getLogger().debug("Found file: " + key);
111             }
112             try
113             {
114                 return this.deserializeObject(file);
115             }
116             catch (Exception any) {
117                 getLogger().error("Error during deseralization.", any);
118             }
119         }
120         else
121         {
122             if (getLogger().isDebugEnabled())
123             {
124                 getLogger().debug("NOT Found file: " + key);
125             }
126         }
127
128         return null;
129     }
130
131     /**
132      * Store the given object in a persistent state.
133      * 1) Null values generate empty directories.
134      * 2) String values are dumped to text files
135      * 3) Object values are serialized
136      */

137     protected void doStore(final Object key, final Object value)
138     throws IOException
139      {
140         final File file = fileFromKey(key);
141
142         /* Create subdirectories as needed */
143         final File parent = file.getParentFile();
144         if (parent != null)
145         {
146             parent.mkdirs();
147         }
148
149         /* Store object as file */
150         if (value == null)
151         { /* Directory */
152             if (file.exists())
153             {
154                 if (!file.delete())
155                 { /* FAILURE */
156                     getLogger().error("File cannot be deleted: " + file.toString());
157                     return;
158                 }
159             }
160
161             file.mkdir();
162         }
163         else if (value instanceof String)
164         {
165             /* Text file */
166             this.serializeString(file, (String) value);
167         }
168         else
169         {
170             /* Serialized Object */
171             this.serializeObject(file, value);
172         }
173     }
174
175     /**
176      * Remove the object associated to the given key.
177      */

178     protected void doRemove(final Object key)
179     {
180         final File file = fileFromKey(key);
181         if (file != null)
182         {
183             file.delete();
184         }
185     }
186
187     /**
188      * Clear the Store of all elements
189      */

190     protected void doClear()
191     {
192         Enumeration enum = this.keys();
193         while (enum.hasMoreElements())
194         {
195             Object key = enum.nextElement();
196             if (key == null)
197             {
198                 continue;
199             }
200             this.remove(key);
201         }
202     }
203
204     /**
205      * Indicates if the given key is associated to a contained object.
206      */

207     protected boolean doContainsKey(final Object key)
208     {
209         final File file = fileFromKey(key);
210         if (file == null)
211         {
212             return false;
213         }
214         return file.exists();
215     }
216
217     /**
218      * Returns the list of stored files as an Enumeration of Files
219      */

220     protected Enumeration doGetKeys()
221     {
222         final FSEnumeration enum = new FSEnumeration();
223         this.addKeys(enum, this.m_directoryFile);
224         return enum;
225     }
226
227     /**
228      * Returns count of the objects in the store, or -1 if could not be
229      * obtained.
230      */

231     protected int doGetSize()
232     {
233         return countKeys(this.m_directoryFile);
234     }
235
236     protected void addKeys(FSEnumeration enum, File directory)
237     {
238         final int subStringBegin = this.m_directoryFile.getAbsolutePath().length() + 1;
239         final File[] files = directory.listFiles();
240         for (int i=0; i<files.length; i++)
241          {
242             if (files[i].isDirectory())
243             {
244                 this.addKeys(enum, files[i]);
245             }
246             else
247             {
248                 enum.add(this.decode(files[i].getAbsolutePath().substring(subStringBegin)));
249             }
250         }
251     }
252
253     protected int countKeys(File directory)
254     {
255         int count = 0;
256         final File[] files = directory.listFiles();
257         for (int i=0; i<files.length; i++)
258         {
259             if (files[i].isDirectory())
260             {
261                 count += this.countKeys(files[i]);
262             }
263             else
264             {
265                 count ++;
266             }
267         }
268         return count;
269     }
270
271     final class FSEnumeration implements Enumeration
272     {
273         private String[] array;
274         private int index;
275         private int length;
276
277         FSEnumeration()
278         {
279             this.array = new String[16];
280             this.length = 0;
281             this.index = 0;
282         }
283
284         public void add(String key)
285         {
286             if (this.length == array.length)
287             {
288                 String[] newarray = new String[this.length + 16];
289                 System.arraycopy(this.array, 0, newarray, 0, this.array.length);
290                 this.array = newarray;
291             }
292             this.array[this.length] = key;
293             this.length++;
294         }
295
296         public boolean hasMoreElements()
297         {
298             return (this.index < this.length);
299         }
300
301         public Object nextElement()
302         {
303             if (this.hasMoreElements())
304             {
305                 this.index++;
306                 return this.array[index-1];
307             }
308             return null;
309         }
310     }
311
312     /* Utility Methods*/
313     protected File fileFromKey(final Object key)
314     {
315         File file = new File(this.m_directoryFile, this.encode(key.toString()));
316         File parent = file.getParentFile();
317         if (parent != null) parent.mkdirs();
318         return file;
319     }
320
321     public String getString(final Object key)
322     throws IOException
323     {
324         final File file = this.fileFromKey(key);
325         if (file != null)
326         {
327             return this.deserializeString(file);
328         }
329
330         return null;
331     }
332
333     public void free()
334     {
335         // if we ever implement this, we should implement doFree()
336
}
337
338     /* (non-Javadoc)
339      * @see org.apache.excalibur.store.impl.AbstractReadWriteStore#doFree()
340      */

341     protected void doFree()
342     {
343     }
344
345     public synchronized Object getObject(final Object key)
346     throws IOException, ClassNotFoundException
347     {
348         Sync sync = this.lock.writeLock();
349         try
350         {
351             sync.acquire();
352             try
353             {
354                 final File file = this.fileFromKey(key);
355                 if (file != null) {
356                     return this.deserializeObject(file);
357                 }
358             }
359             finally
360             {
361                 sync.release();
362             }
363         }
364         catch (InterruptedException ignore)
365         {
366         }
367         
368         return null;
369     }
370
371     /**
372      * Inverse of encode exept it do not use path.
373      * So decode(encode(s) - m_path) = s.
374      * In other words it returns a String that can be used as key to retive
375      * the record contained in the 'filename' file.
376      */

377     protected String decode( String filename )
378     {
379         // if the key is longer than 127 bytes a File.separator
380
// is added each 127 bytes
381
if (filename.length() > 127)
382         {
383             int c = filename.length() / 127;
384             int pos = c * 127;
385             StringBuffer out = new StringBuffer(filename);
386             while (pos > 0) {
387                 out.delete(pos,pos+1);
388                 pos -= 127;
389             }
390             filename = out.toString();
391         }
392         // In JDK 1.4 this is deprecated, the new format is below
393
return java.net.URLDecoder.decode( filename );
394         // return java.net.URLDecoder.decode( filename, "UTF-8" );
395
}
396
397     /** A BitSet defining the characters which don't need encoding */
398     static BitSet charactersDontNeedingEncoding;
399     static final int characterCaseDiff = ('a' - 'A');
400
401     /** Initialize the BitSet */
402     static
403     {
404         charactersDontNeedingEncoding = new BitSet(256);
405         int i;
406         for (i = 'a'; i <= 'z'; i++)
407         {
408             charactersDontNeedingEncoding.set(i);
409         }
410         for (i = 'A'; i <= 'Z'; i++)
411         {
412             charactersDontNeedingEncoding.set(i);
413         }
414         for (i = '0'; i <= '9'; i++)
415         {
416             charactersDontNeedingEncoding.set(i);
417         }
418         charactersDontNeedingEncoding.set('-');
419         charactersDontNeedingEncoding.set('_');
420         charactersDontNeedingEncoding.set('(');
421         charactersDontNeedingEncoding.set(')');
422     }
423
424     /**
425      * Returns a String that uniquely identifies the object.
426      * <b>Note:</b> since this method uses the Object.toString()
427      * method, it's up to the caller to make sure that this method
428      * doesn't change between different JVM executions (like
429      * it may normally happen). For this reason, it's highly recommended
430      * (even if not mandated) that Strings be used as keys.
431      */

432     protected String encode(String s)
433     {
434         final StringBuffer out = new StringBuffer( s.length() );
435         final ByteArrayOutputStream buf = new ByteArrayOutputStream( 32 );
436         final OutputStreamWriter writer = new OutputStreamWriter( buf );
437         for (int i = 0; i < s.length(); i++)
438         {
439             int c = s.charAt(i);
440             if (charactersDontNeedingEncoding.get(c))
441             {
442                 out.append((char)c);
443             }
444             else
445             {
446                 try
447                 {
448                     writer.write(c);
449                     writer.flush();
450                 }
451                 catch(IOException e)
452                 {
453                     buf.reset();
454                     continue;
455                 }
456                 byte[] ba = buf.toByteArray();
457                 for (int j = 0; j < ba.length; j++)
458                 {
459                     out.append('%');
460                     char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
461                     // converting to use uppercase letter as part of
462
// the hex value if ch is a letter.
463
if (Character.isLetter(ch))
464                     {
465                         ch -= characterCaseDiff;
466                     }
467                     out.append(ch);
468                     ch = Character.forDigit(ba[j] & 0xF, 16);
469                     if (Character.isLetter(ch))
470                     {
471                         ch -= characterCaseDiff;
472                     }
473                     out.append(ch);
474                 }
475                 buf.reset();
476             }
477         }
478
479         // if the key is longer than 127 bytes add a File.separator
480
// each 127 bytes
481
int pos = 127;
482         while (out.length() > pos) {
483             out.insert(pos, File.separatorChar);
484             pos += 127;
485         }
486         return out.toString();
487     }
488
489     /**
490      * Dump a <code>String</code> to a text file.
491      *
492      * @param file The output file
493      * @param string The string to be dumped
494      * @exception IOException IO Error
495      */

496     public void serializeString(File file, String string)
497     throws IOException
498     {
499         final Writer fw = new FileWriter(file);
500         try
501         {
502             fw.write(string);
503             fw.flush();
504         }
505         finally
506         {
507             if (fw != null) fw.close();
508         }
509     }
510
511     /**
512      * Load a text file contents as a <code>String<code>.
513      * This method does not perform enconding conversions
514      *
515      * @param file The input file
516      * @return The file contents as a <code>String</code>
517      * @exception IOException IO Error
518      */

519     public String deserializeString(File file)
520     throws IOException
521     {
522         int len;
523         char[] chr = new char[4096];
524         final StringBuffer buffer = new StringBuffer();
525         final FileReader reader = new FileReader(file);
526         try
527         {
528             while ((len = reader.read(chr)) > 0)
529             {
530                 buffer.append(chr, 0, len);
531             }
532         }
533         finally
534         {
535             if (reader != null) reader.close();
536         }
537         return buffer.toString();
538     }
539
540     /**
541      * This method serializes an object to an output stream.
542      *
543      * @param file The output file
544      * @param object The object to be serialized
545      * @exception IOException IOError
546      */

547
548     public void serializeObject(File file, Object object)
549     throws IOException
550     {
551         FileOutputStream fos = new FileOutputStream(file);
552         try
553         {
554             ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(fos));
555             oos.writeObject(object);
556             oos.flush();
557         }
558         finally
559         {
560             if (fos != null) fos.close();
561         }
562     }
563
564     /**
565      * This method deserializes an object from an input stream.
566      *
567      * @param file The input file
568      * @return The deserialized object
569      * @exception IOException IOError
570      */

571     public Object deserializeObject(File file)
572     throws IOException, ClassNotFoundException
573     {
574         FileInputStream fis = new FileInputStream(file);
575         Object object = null;
576         try
577         {
578             ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(fis));
579             object = ois.readObject();
580         }
581         finally
582         {
583             if (fis != null) fis.close();
584         }
585         return object;
586     }
587
588     /**
589      * Get the complete filename corresponding to a (typically relative)
590      * <code>File</code>.
591      * This method accounts for the possibility of an error in getting
592      * the filename's <i>canonical</i> path, returning the io/error-safe
593      * <i>absolute</i> form instead
594      *
595      * @param file The file
596      * @return The file's absolute filename
597      */

598     public String getFullFilename(File file)
599     {
600         try
601         {
602             return file.getCanonicalPath();
603         }
604         catch (Exception e)
605         {
606             return file.getAbsolutePath();
607         }
608     }
609
610 }
611
Popular Tags