Save This Page
Home » lucene-2.4.1-src » org.apache » lucene » index » [javadoc | source]
    1   package org.apache.lucene.index;
    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.IOException;
   21   import java.io.FileNotFoundException;
   22   
   23   import java.util.HashSet;
   24   import java.util.Collection;
   25   import java.util.ArrayList;
   26   import java.util.List;
   27   
   28   import org.apache.lucene.store.Directory;
   29   import org.apache.lucene.store.Lock;
   30   import org.apache.lucene.store.LockObtainFailedException;
   31   import org.apache.lucene.store.FSDirectory;
   32   
   33   /**
   34    * IndexReader implementation that has access to a Directory. 
   35    * Instances that have a SegmentInfos object (i. e. segmentInfos != null)
   36    * "own" the directory, which means that they try to acquire a write lock
   37    * whenever index modifications are performed.
   38    */
   39   abstract class DirectoryIndexReader extends IndexReader {
   40     protected Directory directory;
   41     protected boolean closeDirectory;
   42     private IndexDeletionPolicy deletionPolicy;
   43   
   44     private SegmentInfos segmentInfos;
   45     private Lock writeLock;
   46     private boolean stale;
   47     private final HashSet synced = new HashSet();
   48   
   49     /** Used by commit() to record pre-commit state in case
   50      * rollback is necessary */
   51     private boolean rollbackHasChanges;
   52     private SegmentInfos rollbackSegmentInfos;
   53   
   54     protected boolean readOnly;
   55   
   56     
   57     void init(Directory directory, SegmentInfos segmentInfos, boolean closeDirectory, boolean readOnly)
   58       throws IOException {
   59       this.directory = directory;
   60       this.segmentInfos = segmentInfos;
   61       this.closeDirectory = closeDirectory;
   62       this.readOnly = readOnly;
   63   
   64       if (!readOnly && segmentInfos != null) {
   65         // We assume that this segments_N was previously
   66         // properly sync'd:
   67         for(int i=0;i<segmentInfos.size();i++) {
   68           final SegmentInfo info = segmentInfos.info(i);
   69           List files = info.files();
   70           for(int j=0;j<files.size();j++)
   71             synced.add(files.get(j));
   72         }
   73       }
   74     }
   75     
   76     protected DirectoryIndexReader() {}
   77     
   78     DirectoryIndexReader(Directory directory, SegmentInfos segmentInfos,
   79                          boolean closeDirectory, boolean readOnly) throws IOException {
   80       super();
   81       init(directory, segmentInfos, closeDirectory, readOnly);
   82     }
   83     
   84     static DirectoryIndexReader open(final Directory directory, final boolean closeDirectory, final IndexDeletionPolicy deletionPolicy) throws CorruptIndexException, IOException {
   85       return open(directory, closeDirectory, deletionPolicy, null, false);
   86     }
   87   
   88     static DirectoryIndexReader open(final Directory directory, final boolean closeDirectory, final IndexDeletionPolicy deletionPolicy, final IndexCommit commit, final boolean readOnly) throws CorruptIndexException, IOException {
   89   
   90       SegmentInfos.FindSegmentsFile finder = new SegmentInfos.FindSegmentsFile(directory) {
   91   
   92         protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
   93   
   94           SegmentInfos infos = new SegmentInfos();
   95           infos.read(directory, segmentFileName);
   96   
   97           DirectoryIndexReader reader;
   98   
   99           if (infos.size() == 1) {          // index is optimized
  100             reader = SegmentReader.get(readOnly, infos, infos.info(0), false);
  101           } else if (readOnly) {
  102             reader = new ReadOnlyMultiSegmentReader(directory, infos, false);
  103           } else {
  104             reader = new MultiSegmentReader(directory, infos, false, false);
  105           }
  106           reader.setDeletionPolicy(deletionPolicy);
  107           reader.closeDirectory = closeDirectory;
  108           return reader;
  109         }
  110       };
  111   
  112       DirectoryIndexReader reader = null;
  113       try {
  114         if (commit == null)
  115           reader = (DirectoryIndexReader) finder.run();
  116         else {
  117           if (directory != commit.getDirectory())
  118             throw new IOException("the specified commit does not match the specified Directory");
  119           // This can & will directly throw IOException if the
  120           // specified commit point has been deleted:
  121           reader = (DirectoryIndexReader) finder.doBody(commit.getSegmentsFileName());
  122         }
  123       } finally {
  124         // We passed false above for closeDirectory so that
  125         // the directory would not be closed before we were
  126         // done retrying, so at this point if we truly failed
  127         // to open a reader, which means an exception is being
  128         // thrown, then close the directory now:
  129         if (reader == null && closeDirectory) {
  130           try {
  131             directory.close();
  132           } catch (IOException ioe) {
  133             // suppress, so we keep throwing original failure
  134             // from opening the reader
  135           }
  136         }
  137       }
  138   
  139       return reader;
  140     }
  141   
  142     public final synchronized IndexReader reopen() throws CorruptIndexException, IOException {
  143       ensureOpen();
  144   
  145       if (this.hasChanges || this.isCurrent()) {
  146         // this has changes, therefore we have the lock and don't need to reopen
  147         // OR: the index in the directory hasn't changed - nothing to do here
  148         return this;
  149       }
  150   
  151       final SegmentInfos.FindSegmentsFile finder = new SegmentInfos.FindSegmentsFile(directory) {
  152   
  153         protected Object doBody(String segmentFileName) throws CorruptIndexException, IOException {
  154           SegmentInfos infos = new SegmentInfos();
  155           infos.read(directory, segmentFileName);
  156   
  157           DirectoryIndexReader newReader = doReopen(infos);
  158           
  159           if (DirectoryIndexReader.this != newReader) {
  160             newReader.init(directory, infos, closeDirectory, readOnly);
  161             newReader.deletionPolicy = deletionPolicy;
  162           }
  163   
  164           return newReader;
  165         }
  166       };
  167   
  168       DirectoryIndexReader reader = null;
  169   
  170       // While trying to reopen, we temporarily mark our
  171       // closeDirectory as false.  This way any exceptions hit
  172       // partway while opening the reader, which is expected
  173       // eg if writer is committing, won't close our
  174       // directory.  We restore this value below:
  175       final boolean myCloseDirectory = closeDirectory;
  176       closeDirectory = false;
  177   
  178       try {
  179         reader = (DirectoryIndexReader) finder.run();
  180       } finally {
  181         if (myCloseDirectory) {
  182           assert directory instanceof FSDirectory;
  183           // Restore my closeDirectory
  184           closeDirectory = true;
  185           if (reader != null && reader != this) {
  186             // Success, and a new reader was actually opened
  187             reader.closeDirectory = true;
  188             // Clone the directory
  189             reader.directory = FSDirectory.getDirectory(((FSDirectory) directory).getFile());
  190           }
  191         }
  192       }
  193   
  194       return reader;
  195     }
  196   
  197     /**
  198      * Re-opens the index using the passed-in SegmentInfos 
  199      */
  200     protected abstract DirectoryIndexReader doReopen(SegmentInfos infos) throws CorruptIndexException, IOException;
  201     
  202     public void setDeletionPolicy(IndexDeletionPolicy deletionPolicy) {
  203       this.deletionPolicy = deletionPolicy;
  204     }
  205     
  206     /** Returns the directory this index resides in.
  207      */
  208     public Directory directory() {
  209       ensureOpen();
  210       return directory;
  211     }
  212   
  213     /**
  214      * Version number when this IndexReader was opened.
  215      */
  216     public long getVersion() {
  217       ensureOpen();
  218       return segmentInfos.getVersion();
  219     }
  220   
  221     /**
  222      * Check whether this IndexReader is still using the
  223      * current (i.e., most recently committed) version of the
  224      * index.  If a writer has committed any changes to the
  225      * index since this reader was opened, this will return
  226      * <code>false</code>, in which case you must open a new
  227      * IndexReader in order to see the changes.  See the
  228      * description of the <a href="IndexWriter.html#autoCommit"><code>autoCommit</code></a>
  229      * flag which controls when the {@link IndexWriter}
  230      * actually commits changes to the index.
  231      * 
  232      * @throws CorruptIndexException if the index is corrupt
  233      * @throws IOException if there is a low-level IO error
  234      */
  235     public boolean isCurrent() throws CorruptIndexException, IOException {
  236       ensureOpen();
  237       return SegmentInfos.readCurrentVersion(directory) == segmentInfos.getVersion();
  238     }
  239   
  240     /**
  241      * Checks is the index is optimized (if it has a single segment and no deletions)
  242      * @return <code>true</code> if the index is optimized; <code>false</code> otherwise
  243      */
  244     public boolean isOptimized() {
  245       ensureOpen();
  246       return segmentInfos.size() == 1 && hasDeletions() == false;
  247     }
  248   
  249     protected void doClose() throws IOException {
  250       if(closeDirectory)
  251         directory.close();
  252     }
  253     
  254     /**
  255      * Commit changes resulting from delete, undeleteAll, or
  256      * setNorm operations
  257      *
  258      * If an exception is hit, then either no changes or all
  259      * changes will have been committed to the index
  260      * (transactional semantics).
  261      * @throws IOException if there is a low-level IO error
  262      */
  263     protected void doCommit() throws IOException {
  264       if (hasChanges) {
  265         if (segmentInfos != null) {
  266   
  267           // Default deleter (for backwards compatibility) is
  268           // KeepOnlyLastCommitDeleter:
  269           IndexFileDeleter deleter =  new IndexFileDeleter(directory,
  270                                                            deletionPolicy == null ? new KeepOnlyLastCommitDeletionPolicy() : deletionPolicy,
  271                                                            segmentInfos, null, null);
  272   
  273           // Checkpoint the state we are about to change, in
  274           // case we have to roll back:
  275           startCommit();
  276   
  277           boolean success = false;
  278           try {
  279             commitChanges();
  280   
  281             // Sync all files we just wrote
  282             for(int i=0;i<segmentInfos.size();i++) {
  283               final SegmentInfo info = segmentInfos.info(i);
  284               final List files = info.files();
  285               for(int j=0;j<files.size();j++) {
  286                 final String fileName = (String) files.get(j);
  287                 if (!synced.contains(fileName)) {
  288                   assert directory.fileExists(fileName);
  289                   directory.sync(fileName);
  290                   synced.add(fileName);
  291                 }
  292               }
  293             }
  294   
  295             segmentInfos.commit(directory);
  296             success = true;
  297           } finally {
  298   
  299             if (!success) {
  300   
  301               // Rollback changes that were made to
  302               // SegmentInfos but failed to get [fully]
  303               // committed.  This way this reader instance
  304               // remains consistent (matched to what's
  305               // actually in the index):
  306               rollbackCommit();
  307   
  308               // Recompute deletable files & remove them (so
  309               // partially written .del files, etc, are
  310               // removed):
  311               deleter.refresh();
  312             }
  313           }
  314   
  315           // Have the deleter remove any now unreferenced
  316           // files due to this commit:
  317           deleter.checkpoint(segmentInfos, true);
  318   
  319           if (writeLock != null) {
  320             writeLock.release();  // release write lock
  321             writeLock = null;
  322           }
  323         }
  324         else
  325           commitChanges();
  326       }
  327       hasChanges = false;
  328     }
  329     
  330     protected abstract void commitChanges() throws IOException;
  331     
  332     /**
  333      * Tries to acquire the WriteLock on this directory.
  334      * this method is only valid if this IndexReader is directory owner.
  335      * 
  336      * @throws StaleReaderException if the index has changed
  337      * since this reader was opened
  338      * @throws CorruptIndexException if the index is corrupt
  339      * @throws LockObtainFailedException if another writer
  340      *  has this index open (<code>write.lock</code> could not
  341      *  be obtained)
  342      * @throws IOException if there is a low-level IO error
  343      */
  344     protected void acquireWriteLock() throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException {
  345       if (segmentInfos != null) {
  346         ensureOpen();
  347         if (stale)
  348           throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
  349     
  350         if (writeLock == null) {
  351           Lock writeLock = directory.makeLock(IndexWriter.WRITE_LOCK_NAME);
  352           if (!writeLock.obtain(IndexWriter.WRITE_LOCK_TIMEOUT)) // obtain write lock
  353             throw new LockObtainFailedException("Index locked for write: " + writeLock);
  354           this.writeLock = writeLock;
  355     
  356           // we have to check whether index has changed since this reader was opened.
  357           // if so, this reader is no longer valid for deletion
  358           if (SegmentInfos.readCurrentVersion(directory) > segmentInfos.getVersion()) {
  359             stale = true;
  360             this.writeLock.release();
  361             this.writeLock = null;
  362             throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations");
  363           }
  364         }
  365       }
  366     }
  367   
  368     /**
  369      * Should internally checkpoint state that will change
  370      * during commit so that we can rollback if necessary.
  371      */
  372     void startCommit() {
  373       if (segmentInfos != null) {
  374         rollbackSegmentInfos = (SegmentInfos) segmentInfos.clone();
  375       }
  376       rollbackHasChanges = hasChanges;
  377     }
  378   
  379     /**
  380      * Rolls back state to just before the commit (this is
  381      * called by commit() if there is some exception while
  382      * committing).
  383      */
  384     void rollbackCommit() {
  385       if (segmentInfos != null) {
  386         for(int i=0;i<segmentInfos.size();i++) {
  387           // Rollback each segmentInfo.  Because the
  388           // SegmentReader holds a reference to the
  389           // SegmentInfo we can't [easily] just replace
  390           // segmentInfos, so we reset it in place instead:
  391           segmentInfos.info(i).reset(rollbackSegmentInfos.info(i));
  392         }
  393         rollbackSegmentInfos = null;
  394       }
  395   
  396       hasChanges = rollbackHasChanges;
  397     }
  398   
  399     /** Release the write lock, if needed. */
  400     protected void finalize() throws Throwable {
  401       try {
  402         if (writeLock != null) {
  403           writeLock.release();                        // release write lock
  404           writeLock = null;
  405         }
  406       } finally {
  407         super.finalize();
  408       }
  409     }
  410   
  411     private static class ReaderCommit extends IndexCommit {
  412       private String segmentsFileName;
  413       Collection files;
  414       Directory dir;
  415       long generation;
  416       long version;
  417       final boolean isOptimized;
  418   
  419       ReaderCommit(SegmentInfos infos, Directory dir) throws IOException {
  420         segmentsFileName = infos.getCurrentSegmentFileName();
  421         this.dir = dir;
  422         final int size = infos.size();
  423         files = new ArrayList(size);
  424         files.add(segmentsFileName);
  425         for(int i=0;i<size;i++) {
  426           SegmentInfo info = infos.info(i);
  427           if (info.dir == dir)
  428             files.addAll(info.files());
  429         }
  430         version = infos.getVersion();
  431         generation = infos.getGeneration();
  432         isOptimized = infos.size() == 1 && !infos.info(0).hasDeletions();
  433       }
  434   
  435       public boolean isOptimized() {
  436         return isOptimized;
  437       }
  438       public String getSegmentsFileName() {
  439         return segmentsFileName;
  440       }
  441       public Collection getFileNames() {
  442         return files;
  443       }
  444       public Directory getDirectory() {
  445         return dir;
  446       }
  447       public long getVersion() {
  448         return version;
  449       }
  450       public long getGeneration() {
  451         return generation;
  452       }
  453       public boolean isDeleted() {
  454         return false;
  455       }
  456     }
  457   
  458     /**
  459      * Expert: return the IndexCommit that this reader has
  460      * opened.
  461      *
  462      * <p><b>WARNING</b>: this API is new and experimental and
  463      * may suddenly change.</p>
  464      */
  465     public IndexCommit getIndexCommit() throws IOException {
  466       return new ReaderCommit(segmentInfos, directory);
  467     }
  468   
  469     /** @see IndexReader#listCommits */
  470     public static Collection listCommits(Directory dir) throws IOException {
  471   
  472       final String[] files = dir.list();
  473       if (files == null)
  474         throw new IOException("cannot read directory " + dir + ": list() returned null");
  475   
  476       Collection commits = new ArrayList();
  477   
  478       SegmentInfos latest = new SegmentInfos();
  479       latest.read(dir);
  480       final long currentGen = latest.getGeneration();
  481   
  482       commits.add(new ReaderCommit(latest, dir));
  483       
  484       for(int i=0;i<files.length;i++) {
  485   
  486         final String fileName = files[i];
  487   
  488         if (fileName.startsWith(IndexFileNames.SEGMENTS) &&
  489             !fileName.equals(IndexFileNames.SEGMENTS_GEN) &&
  490             SegmentInfos.generationFromSegmentsFileName(fileName) < currentGen) {
  491   
  492           SegmentInfos sis = new SegmentInfos();
  493           try {
  494             // IOException allowed to throw there, in case
  495             // segments_N is corrupt
  496             sis.read(dir, fileName);
  497           } catch (FileNotFoundException fnfe) {
  498             // LUCENE-948: on NFS (and maybe others), if
  499             // you have writers switching back and forth
  500             // between machines, it's very likely that the
  501             // dir listing will be stale and will claim a
  502             // file segments_X exists when in fact it
  503             // doesn't.  So, we catch this and handle it
  504             // as if the file does not exist
  505             sis = null;
  506           }
  507   
  508           if (sis != null)
  509             commits.add(new ReaderCommit(sis, dir));
  510         }
  511       }
  512   
  513       return commits;
  514     }
  515   }

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