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

Save This Page
Home » lucene-2.4.1-src » org.apache » lucene » store » [javadoc | source]