diff --git a/source/java/org/apache/lucene/store/AlfrescoFSDirectory.java b/source/java/org/apache/lucene/store/AlfrescoFSDirectory.java new file mode 100644 index 0000000000..ff6191c45d --- /dev/null +++ b/source/java/org/apache/lucene/store/AlfrescoFSDirectory.java @@ -0,0 +1,6 @@ +package org.apache.lucene.store; + +public class AlfrescoFSDirectory extends FSDirectory +{ + +} diff --git a/source/java/org/apache/lucene/store/FSDirectory.java b/source/java/org/apache/lucene/store/FSDirectory.java new file mode 100644 index 0000000000..3b7c289eb2 --- /dev/null +++ b/source/java/org/apache/lucene/store/FSDirectory.java @@ -0,0 +1,526 @@ +package org.apache.lucene.store; + +/** + * Copyright 2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Hashtable; + +import org.apache.lucene.index.IndexFileNameFilter; + +/** + * Straightforward implementation of {@link Directory} as a directory of files. + * + * @see Directory + * @author Doug Cutting + */ +public class FSDirectory extends Directory { + + /** This cache of directories ensures that there is a unique Directory + * instance per path, so that synchronization on the Directory can be used to + * synchronize access between readers and writers. + * + * This should be a WeakHashMap, so that entries can be GC'd, but that would + * require Java 1.2. Instead we use refcounts... + */ + private static final Hashtable DIRECTORIES = new Hashtable(); + + private static boolean disableLocks = false; + + /** + * Set whether Lucene's use of lock files is disabled. By default, + * lock files are enabled. They should only be disabled if the index + * is on a read-only medium like a CD-ROM. + */ + public static void setDisableLocks(boolean doDisableLocks) { + FSDirectory.disableLocks = doDisableLocks; + } + + /** + * Returns whether Lucene's use of lock files is disabled. + * @return true if locks are disabled, false if locks are enabled. + */ + public static boolean getDisableLocks() { + return FSDirectory.disableLocks; + } + + /** + * Directory specified by org.apache.lucene.lockDir + * or java.io.tmpdir system property + */ + public static final String LOCK_DIR = + System.getProperty("org.apache.lucene.lockDir", + System.getProperty("java.io.tmpdir")); + + /** The default class which implements filesystem-based directories. */ + private static Class IMPL; + static { + try { + String name = + System.getProperty("org.apache.lucene.FSDirectory.class", + FSDirectory.class.getName()); + IMPL = Class.forName(name); + } catch (ClassNotFoundException e) { + throw new RuntimeException("cannot load FSDirectory class: " + e.toString(), e); + } catch (SecurityException se) { + try { + IMPL = Class.forName(FSDirectory.class.getName()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("cannot load default FSDirectory class: " + e.toString(), e); + } + } + } + + private static MessageDigest DIGESTER; + + static { + try { + DIGESTER = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e.toString(), e); + } + } + + /** A buffer optionally used in renameTo method */ + private byte[] buffer = null; + + /** Returns the directory instance for the named location. + * + *

Directories are cached, so that, for a given canonical path, the same + * FSDirectory instance will always be returned. This permits + * synchronization on directories. + * + * @param path the path to the directory. + * @param create if true, create, or erase any existing contents. + * @return the FSDirectory for the named file. */ + public static FSDirectory getDirectory(String path, boolean create) + throws IOException { + return getDirectory(new File(path), create); + } + + /** Returns the directory instance for the named location. + * + *

Directories are cached, so that, for a given canonical path, the same + * FSDirectory instance will always be returned. This permits + * synchronization on directories. + * + * @param file the path to the directory. + * @param create if true, create, or erase any existing contents. + * @return the FSDirectory for the named file. */ + public static FSDirectory getDirectory(File file, boolean create) + throws IOException { + file = new File(file.getCanonicalPath()); + FSDirectory dir; + synchronized (DIRECTORIES) { + dir = (FSDirectory)DIRECTORIES.get(file); + if (dir == null) { + try { + dir = (FSDirectory)IMPL.newInstance(); + } catch (Exception e) { + throw new RuntimeException("cannot load FSDirectory class: " + e.toString(), e); + } + dir.init(file, create); + DIRECTORIES.put(file, dir); + } else if (create) { + dir.create(); + } + } + synchronized (dir) { + dir.refCount++; + } + return dir; + } + + private File directory = null; + private int refCount; + private File lockDir; + + protected FSDirectory() {}; // permit subclassing + + private void init(File path, boolean create) throws IOException { + directory = path; + + if (LOCK_DIR == null) { + lockDir = directory; + } + else { + lockDir = new File(LOCK_DIR); + } + // Ensure that lockDir exists and is a directory. + if (!lockDir.exists()) { + if (!lockDir.mkdirs()) + throw new IOException("Cannot create directory: " + lockDir.getAbsolutePath()); + } else if (!lockDir.isDirectory()) { + throw new IOException("Found regular file where directory expected: " + + lockDir.getAbsolutePath()); + } + if (create) { + create(); + } + + if (!directory.isDirectory()) + throw new IOException(path + " not a directory"); + } + + private synchronized void create() throws IOException { + if (!directory.exists()) + if (!directory.mkdirs()) + throw new IOException("Cannot create directory: " + directory); + + if (!directory.isDirectory()) + throw new IOException(directory + " not a directory"); + + String[] files = directory.list(new IndexFileNameFilter()); // clear old files + if (files == null) + throw new IOException("Cannot read directory " + directory.getAbsolutePath()); + for (int i = 0; i < files.length; i++) { + File file = new File(directory, files[i]); + if (!file.delete()) + throw new IOException("Cannot delete " + file); + } + + String lockPrefix = getLockPrefix().toString(); // clear old locks + files = lockDir.list(); + if (files == null) + throw new IOException("Cannot read lock directory " + lockDir.getAbsolutePath()); + for (int i = 0; i < files.length; i++) { + if (!files[i].startsWith(lockPrefix)) + continue; + File lockFile = new File(lockDir, files[i]); + if (!lockFile.delete()) + throw new IOException("Cannot delete " + lockFile); + } + } + + /** Returns an array of strings, one for each file in the directory. */ + public String[] list() { + return directory.list(); + } + + /** Returns true iff a file with the given name exists. */ + public boolean fileExists(String name) { + File file = new File(directory, name); + return file.exists(); + } + + /** Returns the time the named file was last modified. */ + public long fileModified(String name) { + File file = new File(directory, name); + return file.lastModified(); + } + + /** Returns the time the named file was last modified. */ + public static long fileModified(File directory, String name) { + File file = new File(directory, name); + return file.lastModified(); + } + + /** Set the modified time of an existing file to now. */ + public void touchFile(String name) { + File file = new File(directory, name); + file.setLastModified(System.currentTimeMillis()); + } + + /** Returns the length in bytes of a file in the directory. */ + public long fileLength(String name) { + File file = new File(directory, name); + return file.length(); + } + + /** Removes an existing file in the directory. */ + public void deleteFile(String name) throws IOException { + File file = new File(directory, name); + if (!file.delete()) + throw new IOException("Cannot delete " + file); + } + + /** Renames an existing file in the directory. */ + public synchronized void renameFile(String from, String to) + throws IOException { + File old = new File(directory, from); + File nu = new File(directory, to); + + /* This is not atomic. If the program crashes between the call to + delete() and the call to renameTo() then we're screwed, but I've + been unable to figure out how else to do this... */ + + if (nu.exists()) + if (!nu.delete()) + throw new IOException("Cannot delete " + nu); + + // Rename the old file to the new one. Unfortunately, the renameTo() + // method does not work reliably under some JVMs. Therefore, if the + // rename fails, we manually rename by copying the old file to the new one + if (!old.renameTo(nu)) { + java.io.InputStream in = null; + java.io.OutputStream out = null; + try { + in = new FileInputStream(old); + out = new FileOutputStream(nu); + // see if the buffer needs to be initialized. Initialization is + // only done on-demand since many VM's will never run into the renameTo + // bug and hence shouldn't waste 1K of mem for no reason. + if (buffer == null) { + buffer = new byte[1024]; + } + int len; + while ((len = in.read(buffer)) >= 0) { + out.write(buffer, 0, len); + } + + // delete the old file. + old.delete(); + } + catch (IOException ioe) { + IOException newExc = new IOException("Cannot rename " + old + " to " + nu); + newExc.initCause(ioe); + throw newExc; + } + finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + throw new RuntimeException("Cannot close input stream: " + e.toString(), e); + } + } + if (out != null) { + try { + out.close(); + } catch (IOException e) { + throw new RuntimeException("Cannot close output stream: " + e.toString(), e); + } + } + } + } + } + + /** Creates a new, empty file in the directory with the given name. + Returns a stream writing this file. */ + public IndexOutput createOutput(String name) throws IOException { + File file = new File(directory, name); + if (file.exists() && !file.delete()) // delete existing, if any + throw new IOException("Cannot overwrite: " + file); + + return new FSIndexOutput(file); + } + + /** Returns a stream reading an existing file. */ + public IndexInput openInput(String name) throws IOException { + return new FSIndexInput(new File(directory, name)); + } + + /** + * So we can do some byte-to-hexchar conversion below + */ + private static final char[] HEX_DIGITS = + {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; + + /** Constructs a {@link Lock} with the specified name. Locks are implemented + * with {@link File#createNewFile()}. + * + * @param name the name of the lock file + * @return an instance of Lock holding the lock + */ + public Lock makeLock(String name) { + StringBuffer buf = getLockPrefix(); + buf.append("-"); + buf.append(name); + + // create a lock file + final File lockFile = new File(lockDir, buf.toString()); + + return new Lock() { + public boolean obtain() throws IOException { + if (disableLocks) + return true; + + if (!lockDir.exists()) { + if (!lockDir.mkdirs()) { + throw new IOException("Cannot create lock directory: " + lockDir); + } + } + + return lockFile.createNewFile(); + } + public void release() { + if (disableLocks) + return; + lockFile.delete(); + } + public boolean isLocked() { + if (disableLocks) + return false; + return lockFile.exists(); + } + + public String toString() { + return "Lock@" + lockFile; + } + }; + } + + private StringBuffer getLockPrefix() { + String dirName; // name to be hashed + try { + dirName = directory.getCanonicalPath(); + } catch (IOException e) { + throw new RuntimeException(e.toString(), e); + } + + byte digest[]; + synchronized (DIGESTER) { + digest = DIGESTER.digest(dirName.getBytes()); + } + StringBuffer buf = new StringBuffer(); + buf.append("lucene-"); + for (int i = 0; i < digest.length; i++) { + int b = digest[i]; + buf.append(HEX_DIGITS[(b >> 4) & 0xf]); + buf.append(HEX_DIGITS[b & 0xf]); + } + + return buf; + } + + /** Closes the store to future operations. */ + public synchronized void close() { + if (--refCount <= 0) { + synchronized (DIRECTORIES) { + DIRECTORIES.remove(directory); + } + } + } + + public File getFile() { + return directory; + } + + /** For debug output. */ + public String toString() { + return this.getClass().getName() + "@" + directory; + } +} + + +class FSIndexInput extends BufferedIndexInput { + + private class Descriptor extends RandomAccessFile { + public long position; + public Descriptor(File file, String mode) throws IOException { + super(file, mode); + } + } + + private Descriptor file = null; + boolean isClone; + private long length; + + public FSIndexInput(File path) throws IOException { + file = new Descriptor(path, "r"); + file.getChannel(); + length = file.length(); + } + + /** IndexInput methods */ + protected void readInternal(byte[] b, int offset, int len) + throws IOException { + synchronized (file) { + long position = getFilePointer(); + if (position != file.position) { + file.seek(position); + file.position = position; + } + int total = 0; + do { + int i = file.read(b, offset+total, len-total); + if (i == -1) + throw new IOException("read past EOF"); + file.position += i; + total += i; + } while (total < len); + } + } + + public void close() throws IOException { + if (!isClone) + file.close(); + } + + protected void seekInternal(long position) { + } + + public long length() { + return length; + } + + protected void finalize() throws IOException { + close(); // close the file + } + + public Object clone() { + FSIndexInput clone = (FSIndexInput)super.clone(); + clone.isClone = true; + return clone; + } + + /** Method used for testing. Returns true if the underlying + * file descriptor is valid. + */ + boolean isFDValid() throws IOException { + return file.getFD().valid(); + } +} + + +class FSIndexOutput extends BufferedIndexOutput { + RandomAccessFile file = null; + + public FSIndexOutput(File path) throws IOException { + file = new RandomAccessFile(path, "rw"); + file.getChannel(); + } + + /** output methods: */ + public void flushBuffer(byte[] b, int size) throws IOException { + file.write(b, 0, size); + } + public void close() throws IOException { + super.close(); + file.close(); + } + + /** Random-access methods */ + public void seek(long pos) throws IOException { + super.seek(pos); + file.seek(pos); + } + public long length() throws IOException { + return file.length(); + } + + protected void finalize() throws IOException { + file.close(); // close the file + } + +} +