| package com.google.gerrit.lucene; |
| |
| /* |
| * 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 com.google.gerrit.common.Nullable; |
| import java.io.IOException; |
| import org.apache.lucene.index.DirectoryReader; |
| import org.apache.lucene.index.FilterDirectoryReader; |
| import org.apache.lucene.index.FilterLeafReader; |
| import org.apache.lucene.index.IndexReader; |
| import org.apache.lucene.index.IndexWriter; |
| import org.apache.lucene.search.IndexSearcher; |
| import org.apache.lucene.search.ReferenceManager; |
| import org.apache.lucene.search.SearcherFactory; |
| 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 #maybeRefresh}. While it's possible to call |
| * this just before running each query, this is discouraged since it penalizes the unlucky queries |
| * that need to refresh. It's better to use a separate background thread, that periodically calls |
| * {@link #maybeRefresh}. Finally, be sure to call {@link #close} once you are done. |
| * |
| * @see SearcherFactory |
| */ |
| // This file was copied from: |
| // https://github.com/apache/lucene-solr/blob/lucene_solr_5_0/lucene/core/src/java/org/apache/lucene/search/SearcherManager.java |
| // The only change (other than class name and import fixes) |
| // is to skip the check in getSearcher that searcherFactory.newSearcher wraps |
| // the provided searcher exactly. |
| final class WrappableSearcherManager extends ReferenceManager<IndexSearcher> { |
| |
| private final SearcherFactory searcherFactory; |
| |
| /** |
| * 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 DirectoryReader}. 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 DirectoryReader#openIfChanged(DirectoryReader, IndexWriter, boolean)}. |
| * @param searcherFactory An optional {@link SearcherFactory}. Pass <code>null</code> if you don't |
| * require the searcher to be warmed before going live or other custom behavior. |
| * @throws IOException if there is a low-level I/O error |
| */ |
| WrappableSearcherManager( |
| IndexWriter writer, boolean applyAllDeletes, SearcherFactory searcherFactory) |
| throws IOException { |
| // TODO(davido): Make it configurable |
| // If true, new deletes will be written down to index files instead of carried over from writer |
| // to reader directly in heap |
| boolean writeAllDeletes = false; |
| if (searcherFactory == null) { |
| searcherFactory = new SearcherFactory(); |
| } |
| this.searcherFactory = searcherFactory; |
| current = |
| getSearcher( |
| searcherFactory, DirectoryReader.open(writer, applyAllDeletes, writeAllDeletes)); |
| } |
| |
| /** |
| * Creates and returns a new SearcherManager from the given {@link Directory}. |
| * |
| * @param dir the directory to open the DirectoryReader on. |
| * @param searcherFactory An optional {@link SearcherFactory}. Pass <code>null</code> if you don't |
| * require the searcher to be warmed before going live or other custom behavior. |
| * @throws IOException if there is a low-level I/O error |
| */ |
| WrappableSearcherManager(Directory dir, SearcherFactory searcherFactory) throws IOException { |
| if (searcherFactory == null) { |
| searcherFactory = new SearcherFactory(); |
| } |
| this.searcherFactory = searcherFactory; |
| current = getSearcher(searcherFactory, DirectoryReader.open(dir)); |
| } |
| |
| /** |
| * Creates and returns a new SearcherManager from an existing {@link DirectoryReader}. Note that |
| * this steals the incoming reference. |
| * |
| * @param reader the DirectoryReader. |
| * @param searcherFactory An optional {@link SearcherFactory}. Pass <code>null</code> if you don't |
| * require the searcher to be warmed before going live or other custom behavior. |
| * @throws IOException if there is a low-level I/O error |
| */ |
| WrappableSearcherManager(DirectoryReader reader, SearcherFactory searcherFactory) |
| throws IOException { |
| if (searcherFactory == null) { |
| searcherFactory = new SearcherFactory(); |
| } |
| this.searcherFactory = searcherFactory; |
| this.current = getSearcher(searcherFactory, reader); |
| } |
| |
| @Override |
| protected void decRef(IndexSearcher reference) throws IOException { |
| reference.getIndexReader().decRef(); |
| } |
| |
| @Nullable |
| @Override |
| protected IndexSearcher refreshIfNeeded(IndexSearcher referenceToRefresh) throws IOException { |
| final IndexReader r = referenceToRefresh.getIndexReader(); |
| assert r instanceof DirectoryReader |
| : "searcher's IndexReader should be a DirectoryReader, but got " + r; |
| final IndexReader newReader = DirectoryReader.openIfChanged((DirectoryReader) r); |
| if (newReader == null) { |
| return null; |
| } |
| return getSearcher(searcherFactory, newReader); |
| } |
| |
| @Override |
| protected boolean tryIncRef(IndexSearcher reference) { |
| return reference.getIndexReader().tryIncRef(); |
| } |
| |
| @Override |
| protected int getRefCount(IndexSearcher reference) { |
| return reference.getIndexReader().getRefCount(); |
| } |
| |
| /** |
| * Returns <code>true</code> if no changes have occured since this searcher ie. reader was opened, |
| * otherwise <code>false</code>. |
| * |
| * @see DirectoryReader#isCurrent() |
| */ |
| public boolean isSearcherCurrent() throws IOException { |
| final IndexSearcher searcher = acquire(); |
| try { |
| final IndexReader r = searcher.getIndexReader(); |
| assert r instanceof DirectoryReader |
| : "searcher's IndexReader should be a DirectoryReader, but got " + r; |
| return ((DirectoryReader) r).isCurrent(); |
| } finally { |
| release(searcher); |
| } |
| } |
| |
| /** |
| * Expert: creates a searcher from the provided {@link IndexReader} using the provided {@link |
| * SearcherFactory}. NOTE: this decRefs incoming reader on throwing an exception. |
| */ |
| @SuppressWarnings({"resource", "ReferenceEquality"}) |
| public static IndexSearcher getSearcher(SearcherFactory searcherFactory, IndexReader reader) |
| throws IOException { |
| boolean success = false; |
| final IndexSearcher searcher; |
| try { |
| searcher = searcherFactory.newSearcher(reader, null); |
| // Modification for Gerrit: Allow searcherFactory to transitively wrap the |
| // provided reader. |
| IndexReader unwrapped = searcher.getIndexReader(); |
| while (true) { |
| if (unwrapped == reader) { |
| break; |
| } else if (unwrapped instanceof FilterDirectoryReader) { |
| unwrapped = ((FilterDirectoryReader) unwrapped).getDelegate(); |
| } else if (unwrapped instanceof FilterLeafReader) { |
| unwrapped = ((FilterLeafReader) unwrapped).getDelegate(); |
| } else { |
| break; |
| } |
| } |
| |
| if (unwrapped != reader) { |
| throw new IllegalStateException( |
| "SearcherFactory must wrap the provided reader (got " |
| + searcher.getIndexReader() |
| + " but expected " |
| + reader |
| + ")"); |
| } |
| success = true; |
| } finally { |
| if (!success) { |
| reader.decRef(); |
| } |
| } |
| return searcher; |
| } |
| } |