001: package org.apache.lucene.store;
002:
003: /**
004: * Licensed to the Apache Software Foundation (ASF) under one or more
005: * contributor license agreements. See the NOTICE file distributed with
006: * this work for additional information regarding copyright ownership.
007: * The ASF licenses this file to You under the Apache License, Version 2.0
008: * (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019:
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.FileOutputStream;
023: import java.io.IOException;
024: import java.io.RandomAccessFile;
025: import java.security.MessageDigest;
026: import java.security.NoSuchAlgorithmException;
027: import java.util.Hashtable;
028:
029: import org.apache.lucene.index.IndexFileNameFilter;
030:
031: // Used only for WRITE_LOCK_NAME in deprecated create=true case:
032: import org.apache.lucene.index.IndexWriter;
033:
034: /**
035: * Straightforward implementation of {@link Directory} as a directory of files.
036: * Locking implementation is by default the {@link SimpleFSLockFactory}, but
037: * can be changed either by passing in a {@link LockFactory} instance to
038: * <code>getDirectory</code>, or specifying the LockFactory class by setting
039: * <code>org.apache.lucene.store.FSDirectoryLockFactoryClass</code> Java system
040: * property, or by calling {@link #setLockFactory} after creating
041: * the Directory.
042:
043: * <p>Directories are cached, so that, for a given canonical
044: * path, the same FSDirectory instance will always be
045: * returned by <code>getDirectory</code>. This permits
046: * synchronization on directories.</p>
047: *
048: * @see Directory
049: * @author Doug Cutting
050: */
051: public class FSDirectory extends Directory {
052:
053: /** This cache of directories ensures that there is a unique Directory
054: * instance per path, so that synchronization on the Directory can be used to
055: * synchronize access between readers and writers. We use
056: * refcounts to ensure when the last use of an FSDirectory
057: * instance for a given canonical path is closed, we remove the
058: * instance from the cache. See LUCENE-776
059: * for some relevant discussion.
060: */
061: private static final Hashtable DIRECTORIES = new Hashtable();
062:
063: private static boolean disableLocks = false;
064:
065: // TODO: should this move up to the Directory base class? Also: should we
066: // make a per-instance (in addition to the static "default") version?
067:
068: /**
069: * Set whether Lucene's use of lock files is disabled. By default,
070: * lock files are enabled. They should only be disabled if the index
071: * is on a read-only medium like a CD-ROM.
072: */
073: public static void setDisableLocks(boolean doDisableLocks) {
074: FSDirectory.disableLocks = doDisableLocks;
075: }
076:
077: /**
078: * Returns whether Lucene's use of lock files is disabled.
079: * @return true if locks are disabled, false if locks are enabled.
080: */
081: public static boolean getDisableLocks() {
082: return FSDirectory.disableLocks;
083: }
084:
085: /**
086: * Directory specified by <code>org.apache.lucene.lockDir</code>
087: * or <code>java.io.tmpdir</code> system property.
088:
089: * @deprecated As of 2.1, <code>LOCK_DIR</code> is unused
090: * because the write.lock is now stored by default in the
091: * index directory. If you really want to store locks
092: * elsewhere you can create your own {@link
093: * SimpleFSLockFactory} (or {@link NativeFSLockFactory},
094: * etc.) passing in your preferred lock directory. Then,
095: * pass this <code>LockFactory</code> instance to one of
096: * the <code>getDirectory</code> methods that take a
097: * <code>lockFactory</code> (for example, {@link #getDirectory(String, LockFactory)}).
098: */
099: public static final String LOCK_DIR = System.getProperty(
100: "org.apache.lucene.lockDir", System
101: .getProperty("java.io.tmpdir"));
102:
103: /** The default class which implements filesystem-based directories. */
104: private static Class IMPL;
105: static {
106: try {
107: String name = System.getProperty(
108: "org.apache.lucene.FSDirectory.class",
109: FSDirectory.class.getName());
110: IMPL = Class.forName(name);
111: } catch (ClassNotFoundException e) {
112: throw new RuntimeException(
113: "cannot load FSDirectory class: " + e.toString(), e);
114: } catch (SecurityException se) {
115: try {
116: IMPL = Class.forName(FSDirectory.class.getName());
117: } catch (ClassNotFoundException e) {
118: throw new RuntimeException(
119: "cannot load default FSDirectory class: "
120: + e.toString(), e);
121: }
122: }
123: }
124:
125: private static MessageDigest DIGESTER;
126:
127: static {
128: try {
129: DIGESTER = MessageDigest.getInstance("MD5");
130: } catch (NoSuchAlgorithmException e) {
131: throw new RuntimeException(e.toString(), e);
132: }
133: }
134:
135: /** A buffer optionally used in renameTo method */
136: private byte[] buffer = null;
137:
138: /** Returns the directory instance for the named location.
139: * @param path the path to the directory.
140: * @return the FSDirectory for the named file. */
141: public static FSDirectory getDirectory(String path)
142: throws IOException {
143: return getDirectory(new File(path), null);
144: }
145:
146: /** Returns the directory instance for the named location.
147: * @param path the path to the directory.
148: * @param lockFactory instance of {@link LockFactory} providing the
149: * locking implementation.
150: * @return the FSDirectory for the named file. */
151: public static FSDirectory getDirectory(String path,
152: LockFactory lockFactory) throws IOException {
153: return getDirectory(new File(path), lockFactory);
154: }
155:
156: /** Returns the directory instance for the named location.
157: * @param file the path to the directory.
158: * @return the FSDirectory for the named file. */
159: public static FSDirectory getDirectory(File file)
160: throws IOException {
161: return getDirectory(file, null);
162: }
163:
164: /** Returns the directory instance for the named location.
165: * @param file the path to the directory.
166: * @param lockFactory instance of {@link LockFactory} providing the
167: * locking implementation.
168: * @return the FSDirectory for the named file. */
169: public static FSDirectory getDirectory(File file,
170: LockFactory lockFactory) throws IOException {
171: file = new File(file.getCanonicalPath());
172:
173: if (file.exists() && !file.isDirectory())
174: throw new IOException(file + " not a directory");
175:
176: if (!file.exists())
177: if (!file.mkdirs())
178: throw new IOException("Cannot create directory: "
179: + file);
180:
181: FSDirectory dir;
182: synchronized (DIRECTORIES) {
183: dir = (FSDirectory) DIRECTORIES.get(file);
184: if (dir == null) {
185: try {
186: dir = (FSDirectory) IMPL.newInstance();
187: } catch (Exception e) {
188: throw new RuntimeException(
189: "cannot load FSDirectory class: "
190: + e.toString(), e);
191: }
192: dir.init(file, lockFactory);
193: DIRECTORIES.put(file, dir);
194: } else {
195: // Catch the case where a Directory is pulled from the cache, but has a
196: // different LockFactory instance.
197: if (lockFactory != null
198: && lockFactory != dir.getLockFactory()) {
199: throw new IOException(
200: "Directory was previously created with a different LockFactory instance; please pass null as the lockFactory instance and use setLockFactory to change it");
201: }
202: }
203: }
204: synchronized (dir) {
205: dir.refCount++;
206: }
207: return dir;
208: }
209:
210: /** Returns the directory instance for the named location.
211: *
212: * @deprecated Use IndexWriter's create flag, instead, to
213: * create a new index.
214: *
215: * @param path the path to the directory.
216: * @param create if true, create, or erase any existing contents.
217: * @return the FSDirectory for the named file. */
218: public static FSDirectory getDirectory(String path, boolean create)
219: throws IOException {
220: return getDirectory(new File(path), create);
221: }
222:
223: /** Returns the directory instance for the named location.
224: *
225: * @deprecated Use IndexWriter's create flag, instead, to
226: * create a new index.
227: *
228: * @param file the path to the directory.
229: * @param create if true, create, or erase any existing contents.
230: * @return the FSDirectory for the named file. */
231: public static FSDirectory getDirectory(File file, boolean create)
232: throws IOException {
233: FSDirectory dir = getDirectory(file, null);
234:
235: // This is now deprecated (creation should only be done
236: // by IndexWriter):
237: if (create) {
238: dir.create();
239: }
240:
241: return dir;
242: }
243:
244: private void create() throws IOException {
245: if (directory.exists()) {
246: String[] files = directory.list(IndexFileNameFilter
247: .getFilter()); // clear old files
248: if (files == null)
249: throw new IOException("cannot read directory "
250: + directory.getAbsolutePath()
251: + ": list() returned null");
252: for (int i = 0; i < files.length; i++) {
253: File file = new File(directory, files[i]);
254: if (!file.delete())
255: throw new IOException("Cannot delete " + file);
256: }
257: }
258: lockFactory.clearLock(IndexWriter.WRITE_LOCK_NAME);
259: }
260:
261: private File directory = null;
262: private int refCount;
263:
264: protected FSDirectory() {
265: }; // permit subclassing
266:
267: private void init(File path, LockFactory lockFactory)
268: throws IOException {
269:
270: // Set up lockFactory with cascaded defaults: if an instance was passed in,
271: // use that; else if locks are disabled, use NoLockFactory; else if the
272: // system property org.apache.lucene.store.FSDirectoryLockFactoryClass is set,
273: // instantiate that; else, use SimpleFSLockFactory:
274:
275: directory = path;
276:
277: boolean doClearLockID = false;
278:
279: if (lockFactory == null) {
280:
281: if (disableLocks) {
282: // Locks are disabled:
283: lockFactory = NoLockFactory.getNoLockFactory();
284: } else {
285: String lockClassName = System
286: .getProperty("org.apache.lucene.store.FSDirectoryLockFactoryClass");
287:
288: if (lockClassName != null && !lockClassName.equals("")) {
289: Class c;
290:
291: try {
292: c = Class.forName(lockClassName);
293: } catch (ClassNotFoundException e) {
294: throw new IOException(
295: "unable to find LockClass "
296: + lockClassName);
297: }
298:
299: try {
300: lockFactory = (LockFactory) c.newInstance();
301: } catch (IllegalAccessException e) {
302: throw new IOException(
303: "IllegalAccessException when instantiating LockClass "
304: + lockClassName);
305: } catch (InstantiationException e) {
306: throw new IOException(
307: "InstantiationException when instantiating LockClass "
308: + lockClassName);
309: } catch (ClassCastException e) {
310: throw new IOException(
311: "unable to cast LockClass "
312: + lockClassName
313: + " instance to a LockFactory");
314: }
315:
316: if (lockFactory instanceof NativeFSLockFactory) {
317: ((NativeFSLockFactory) lockFactory)
318: .setLockDir(path);
319: } else if (lockFactory instanceof SimpleFSLockFactory) {
320: ((SimpleFSLockFactory) lockFactory)
321: .setLockDir(path);
322: }
323: } else {
324: // Our default lock is SimpleFSLockFactory;
325: // default lockDir is our index directory:
326: lockFactory = new SimpleFSLockFactory(path);
327: doClearLockID = true;
328: }
329: }
330: }
331:
332: setLockFactory(lockFactory);
333:
334: if (doClearLockID) {
335: // Clear the prefix because write.lock will be
336: // stored in our directory:
337: lockFactory.setLockPrefix(null);
338: }
339: }
340:
341: /** Returns an array of strings, one for each Lucene index file in the directory. */
342: public String[] list() {
343: return directory.list(IndexFileNameFilter.getFilter());
344: }
345:
346: /** Returns true iff a file with the given name exists. */
347: public boolean fileExists(String name) {
348: File file = new File(directory, name);
349: return file.exists();
350: }
351:
352: /** Returns the time the named file was last modified. */
353: public long fileModified(String name) {
354: File file = new File(directory, name);
355: return file.lastModified();
356: }
357:
358: /** Returns the time the named file was last modified. */
359: public static long fileModified(File directory, String name) {
360: File file = new File(directory, name);
361: return file.lastModified();
362: }
363:
364: /** Set the modified time of an existing file to now. */
365: public void touchFile(String name) {
366: File file = new File(directory, name);
367: file.setLastModified(System.currentTimeMillis());
368: }
369:
370: /** Returns the length in bytes of a file in the directory. */
371: public long fileLength(String name) {
372: File file = new File(directory, name);
373: return file.length();
374: }
375:
376: /** Removes an existing file in the directory. */
377: public void deleteFile(String name) throws IOException {
378: File file = new File(directory, name);
379: if (!file.delete())
380: throw new IOException("Cannot delete " + file);
381: }
382:
383: /** Renames an existing file in the directory.
384: * Warning: This is not atomic.
385: * @deprecated
386: */
387: public synchronized void renameFile(String from, String to)
388: throws IOException {
389: File old = new File(directory, from);
390: File nu = new File(directory, to);
391:
392: /* This is not atomic. If the program crashes between the call to
393: delete() and the call to renameTo() then we're screwed, but I've
394: been unable to figure out how else to do this... */
395:
396: if (nu.exists())
397: if (!nu.delete())
398: throw new IOException("Cannot delete " + nu);
399:
400: // Rename the old file to the new one. Unfortunately, the renameTo()
401: // method does not work reliably under some JVMs. Therefore, if the
402: // rename fails, we manually rename by copying the old file to the new one
403: if (!old.renameTo(nu)) {
404: java.io.InputStream in = null;
405: java.io.OutputStream out = null;
406: try {
407: in = new FileInputStream(old);
408: out = new FileOutputStream(nu);
409: // see if the buffer needs to be initialized. Initialization is
410: // only done on-demand since many VM's will never run into the renameTo
411: // bug and hence shouldn't waste 1K of mem for no reason.
412: if (buffer == null) {
413: buffer = new byte[1024];
414: }
415: int len;
416: while ((len = in.read(buffer)) >= 0) {
417: out.write(buffer, 0, len);
418: }
419:
420: // delete the old file.
421: old.delete();
422: } catch (IOException ioe) {
423: IOException newExc = new IOException("Cannot rename "
424: + old + " to " + nu);
425: newExc.initCause(ioe);
426: throw newExc;
427: } finally {
428: try {
429: if (in != null) {
430: try {
431: in.close();
432: } catch (IOException e) {
433: throw new RuntimeException(
434: "Cannot close input stream: "
435: + e.toString(), e);
436: }
437: }
438: } finally {
439: if (out != null) {
440: try {
441: out.close();
442: } catch (IOException e) {
443: throw new RuntimeException(
444: "Cannot close output stream: "
445: + e.toString(), e);
446: }
447: }
448: }
449: }
450: }
451: }
452:
453: /** Creates a new, empty file in the directory with the given name.
454: Returns a stream writing this file. */
455: public IndexOutput createOutput(String name) throws IOException {
456:
457: File file = new File(directory, name);
458: if (file.exists() && !file.delete()) // delete existing, if any
459: throw new IOException("Cannot overwrite: " + file);
460:
461: return new FSIndexOutput(file);
462: }
463:
464: // Inherit javadoc
465: public IndexInput openInput(String name) throws IOException {
466: return new FSIndexInput(new File(directory, name));
467: }
468:
469: // Inherit javadoc
470: public IndexInput openInput(String name, int bufferSize)
471: throws IOException {
472: return new FSIndexInput(new File(directory, name), bufferSize);
473: }
474:
475: /**
476: * So we can do some byte-to-hexchar conversion below
477: */
478: private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4',
479: '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
480:
481: public String getLockID() {
482: String dirName; // name to be hashed
483: try {
484: dirName = directory.getCanonicalPath();
485: } catch (IOException e) {
486: throw new RuntimeException(e.toString(), e);
487: }
488:
489: byte digest[];
490: synchronized (DIGESTER) {
491: digest = DIGESTER.digest(dirName.getBytes());
492: }
493: StringBuffer buf = new StringBuffer();
494: buf.append("lucene-");
495: for (int i = 0; i < digest.length; i++) {
496: int b = digest[i];
497: buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
498: buf.append(HEX_DIGITS[b & 0xf]);
499: }
500:
501: return buf.toString();
502: }
503:
504: /** Closes the store to future operations. */
505: public synchronized void close() {
506: if (--refCount <= 0) {
507: synchronized (DIRECTORIES) {
508: DIRECTORIES.remove(directory);
509: }
510: }
511: }
512:
513: public File getFile() {
514: return directory;
515: }
516:
517: /** For debug output. */
518: public String toString() {
519: return this .getClass().getName() + "@" + directory;
520: }
521:
522: protected static class FSIndexInput extends BufferedIndexInput {
523:
524: private static class Descriptor extends RandomAccessFile {
525: // remember if the file is open, so that we don't try to close it
526: // more than once
527: private boolean isOpen;
528: long position;
529: final long length;
530:
531: public Descriptor(File file, String mode)
532: throws IOException {
533: super (file, mode);
534: isOpen = true;
535: length = length();
536: }
537:
538: public void close() throws IOException {
539: if (isOpen) {
540: isOpen = false;
541: super .close();
542: }
543: }
544:
545: protected void finalize() throws Throwable {
546: try {
547: close();
548: } finally {
549: super .finalize();
550: }
551: }
552: }
553:
554: private final Descriptor file;
555: boolean isClone;
556:
557: public FSIndexInput(File path) throws IOException {
558: this (path, BufferedIndexInput.BUFFER_SIZE);
559: }
560:
561: public FSIndexInput(File path, int bufferSize)
562: throws IOException {
563: super (bufferSize);
564: file = new Descriptor(path, "r");
565: }
566:
567: /** IndexInput methods */
568: protected void readInternal(byte[] b, int offset, int len)
569: throws IOException {
570: synchronized (file) {
571: long position = getFilePointer();
572: if (position != file.position) {
573: file.seek(position);
574: file.position = position;
575: }
576: int total = 0;
577: do {
578: int i = file.read(b, offset + total, len - total);
579: if (i == -1)
580: throw new IOException("read past EOF");
581: file.position += i;
582: total += i;
583: } while (total < len);
584: }
585: }
586:
587: public void close() throws IOException {
588: // only close the file if this is not a clone
589: if (!isClone)
590: file.close();
591: }
592:
593: protected void seekInternal(long position) {
594: }
595:
596: public long length() {
597: return file.length;
598: }
599:
600: public Object clone() {
601: FSIndexInput clone = (FSIndexInput) super .clone();
602: clone.isClone = true;
603: return clone;
604: }
605:
606: /** Method used for testing. Returns true if the underlying
607: * file descriptor is valid.
608: */
609: boolean isFDValid() throws IOException {
610: return file.getFD().valid();
611: }
612: }
613:
614: protected static class FSIndexOutput extends BufferedIndexOutput {
615: RandomAccessFile file = null;
616:
617: // remember if the file is open, so that we don't try to close it
618: // more than once
619: private boolean isOpen;
620:
621: public FSIndexOutput(File path) throws IOException {
622: file = new RandomAccessFile(path, "rw");
623: isOpen = true;
624: }
625:
626: /** output methods: */
627: public void flushBuffer(byte[] b, int offset, int size)
628: throws IOException {
629: file.write(b, offset, size);
630: }
631:
632: public void close() throws IOException {
633: // only close the file if it has not been closed yet
634: if (isOpen) {
635: super .close();
636: file.close();
637: isOpen = false;
638: }
639: }
640:
641: /** Random-access methods */
642: public void seek(long pos) throws IOException {
643: super .seek(pos);
644: file.seek(pos);
645: }
646:
647: public long length() throws IOException {
648: return file.length();
649: }
650:
651: }
652: }
|