content: package org.apache.lucene.search;
  
  /**
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You 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.IOException;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Semaphore;
  
  import org.apache.lucene.index.CorruptIndexException;
  import org.apache.lucene.index.IndexReader;
  import org.apache.lucene.index.IndexWriter;
  import org.apache.lucene.search.NRTManager; // javadocs
  import org.apache.lucene.search.IndexSearcher; // javadocs
  import org.apache.lucene.store.AlreadyClosedException;
  import org.apache.lucene.store.Directory;
  
  /**
   * Utility class to safely share {@link IndexSearcher} instances across multiple
   * threads, while periodically reopening. This class ensures each searcher is
   * closed only once all threads have finished using it.
   * 
   * <p>
   * Use {@link #acquire} to obtain the current searcher, and {@link #release} to
   * release it, like this:
   * 
   * <pre class="prettyprint">
   * IndexSearcher s = manager.acquire();
   * try {
   *   // Do searching, doc retrieval, etc. with s
   * } finally {
   *   manager.release(s);
   * }
   * // Do not use s after this!
   * s = null;
   * </pre>
   * 
   * <p>
   * In addition you should periodically call {@link #maybeReopen}. While it's
   * possible to call this just before running each query, this is discouraged
   * since it penalizes the unlucky queries that do the reopen. It's better to use
   * a separate background thread, that periodically calls maybeReopen. Finally,
   * be sure to call {@link #close} once you are done.
   * 
   * <p>
   * <b>NOTE</b>: if you have an {@link IndexWriter}, it's better to use
   * {@link NRTManager} since that class pulls near-real-time readers from the
   * IndexWriter.
   * 
   * @lucene.experimental
   */
  
  public final class SearcherManager {
  
    private volatile IndexSearcher currentSearcher;
    private final ExecutorService es;
    private final SearcherWarmer warmer;
    private final Semaphore reopenLock = new Semaphore(1);
    
    /**
     * Creates and returns a new SearcherManager from the given {@link IndexWriter}. 
     * @param writer the IndexWriter to open the IndexReader from.
     * @param applyAllDeletes If <code>true</code>, all buffered deletes will
     *        be applied (made visible) in the {@link IndexSearcher} / {@link IndexReader}.
     *        If <code>false</code>, the deletes may or may not be applied, but remain buffered 
     *        (in IndexWriter) so that they will be applied in the future.
     *        Applying deletes can be costly, so if your app can tolerate deleted documents
     *        being returned you might gain some performance by passing <code>false</code>.
     *        See {@link IndexReader#openIfChanged(IndexReader, IndexWriter, boolean)}.
     * @param warmer An optional {@link SearcherWarmer}. Pass
     *        <code>null</code> if you don't require the searcher to warmed
     *        before going live.  If this is  <code>non-null</code> then a
     *        merged segment warmer is installed on the
     *        provided IndexWriter's config.
     * @param es An optional {@link ExecutorService} so different segments can
     *        be searched concurrently (see {@link
     *        IndexSearcher#IndexSearcher(IndexReader,ExecutorService)}.  Pass <code>null</code>
     *        to search segments sequentially.
     *        
     * @throws IOException
     */
    public SearcherManager(IndexWriter writer, boolean applyAllDeletes,
        final SearcherWarmer warmer, final ExecutorService es) throws IOException {
      this.es = es;
      this.warmer = warmer;
      currentSearcher = new IndexSearcher(IndexReader.open(writer, applyAllDeletes));
      if (warmer != null) {
        writer.getConfig().setMergedSegmentWarmer(
            new IndexWriter.IndexReaderWarmer() {
              @Override
              public void warm(IndexReader reader) throws IOException {
                warmer.warm(new IndexSearcher(reader, es));
              }
            });
      }
    }
  
    /**
     * Creates and returns a new SearcherManager from the given {@link Directory}. 
     * @param dir the directory to open the IndexReader on.
     * @param warmer An optional {@link SearcherWarmer}.  Pass
     *        <code>null</code> if you don't require the searcher to warmed
     *        before going live.  If this is  <code>non-null</code> then a
     *        merged segment warmer is installed on the
     *        provided IndexWriter's config.
     * @param es And optional {@link ExecutorService} so different segments can
     *        be searched concurrently (see {@link
     *        IndexSearcher#IndexSearcher(IndexReader,ExecutorService)}.  Pass <code>null</code>
     *        to search segments sequentially.
     *        
     * @throws IOException
     */
    public SearcherManager(Directory dir, SearcherWarmer warmer,
        ExecutorService es) throws IOException {
      this.es = es;
      this.warmer = warmer;
      currentSearcher = new IndexSearcher(IndexReader.open(dir, true), es);
    }
  
    /**
     * You must call this, periodically, to perform a reopen. This calls
     * {@link IndexReader#openIfChanged(IndexReader)} with the underlying reader, and if that returns a
     * new reader, it's warmed (if you provided a {@link SearcherWarmer} and then
     * swapped into production.
     * 
     * <p>
     * <b>Threads</b>: it's fine for more than one thread to call this at once.
     * Only the first thread will attempt the reopen; subsequent threads will see
     * that another thread is already handling reopen and will return immediately.
     * Note that this means if another thread is already reopening then subsequent
     * threads will return right away without waiting for the reader reopen to
     * complete.
     * </p>
     * 
     * <p>
     * This method returns true if a new reader was in fact opened or 
     * if the current searcher has no pending changes.
     * </p>
     */
    public boolean maybeReopen() throws IOException {
      ensureOpen();
      // Ensure only 1 thread does reopen at once; other
      // threads just return immediately:
      if (reopenLock.tryAcquire()) {
        try {
          // IR.openIfChanged preserves NRT and applyDeletes
          // in the newly returned reader:
          final IndexReader newReader = IndexReader.openIfChanged(currentSearcher.getIndexReader());
          if (newReader != null) {
            final IndexSearcher newSearcher = new IndexSearcher(newReader, es);
            boolean success = false;
            try {
              if (warmer != null) {
                warmer.warm(newSearcher);
              }
              swapSearcher(newSearcher);
              success = true;
            } finally {
              if (!success) {
                release(newSearcher);
              }
            }
          }
          return true;
        } finally {
          reopenLock.release();
        }
      } else {
        return false;
      }
    }
    
    /**
     * Returns <code>true</code> if no changes have occured since this searcher
     * ie. reader was opened, otherwise <code>false</code>.
     * @see IndexReader#isCurrent() 
     */
    public boolean isSearcherCurrent() throws CorruptIndexException,
        IOException {
      final IndexSearcher searcher = acquire();
      try {
        return searcher.getIndexReader().isCurrent();
      } finally {
        release(searcher);
      }
    }
  
    /**
     * Release the searcher previously obtained with {@link #acquire}.
     * 
     * <p>
     * <b>NOTE</b>: it's safe to call this after {@link #close}.
     */
    public void release(IndexSearcher searcher) throws IOException {
      assert searcher != null;
      searcher.getIndexReader().decRef();
    }
  
    /**
     * Close this SearcherManager to future searching. Any searches still in
     * process in other threads won't be affected, and they should still call
     * {@link #release} after they are done.
     */
    public synchronized void close() throws IOException {
      if (currentSearcher != null) {
        // make sure we can call this more than once
        // closeable javadoc says:
        // if this is already closed then invoking this method has no effect.
        swapSearcher(null);
      }
    }
  
    /**
     * Obtain the current IndexSearcher. You must match every call to acquire with
     * one call to {@link #release}; it's best to do so in a finally clause.
     */
    public IndexSearcher acquire() {
      IndexSearcher searcher;
      do {
        if ((searcher = currentSearcher) == null) {
          throw new AlreadyClosedException("this SearcherManager is closed");
        }
      } while (!searcher.getIndexReader().tryIncRef());
      return searcher;
    }
  
    private void ensureOpen() {
      if (currentSearcher == null) {
        throw new AlreadyClosedException("this SearcherManager is closed");
      }
    }
  
    private synchronized void swapSearcher(IndexSearcher newSearcher) throws IOException {
      ensureOpen();
      final IndexSearcher oldSearcher = currentSearcher;
      currentSearcher = newSearcher;
      release(oldSearcher);
    }
   
  }
  