package org.apache.blur.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.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.blur.index.ExitableReader.ExitingReaderException; import org.apache.blur.log.Log; import org.apache.blur.log.LogFactory; import org.apache.blur.lucene.search.DeepPagingCache.DeepPageContainer; import org.apache.blur.lucene.search.DeepPagingCache.DeepPageKey; import org.apache.blur.lucene.search.StopExecutionCollector.StopExecutionCollectorException; import org.apache.blur.thrift.BException; import org.apache.blur.thrift.generated.BlurException; import org.apache.blur.thrift.generated.ErrorType; import org.apache.blur.utils.BlurIterable; import org.apache.blur.utils.BlurIterator; import org.apache.lucene.search.Collector; import org.apache.lucene.search.FieldDoc; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.Sort; import org.apache.lucene.search.TopDocs; /** * The {@link IterablePaging} class allows for easy paging through lucene hits. */ public class IterablePaging implements BlurIterable<ScoreDoc, BlurException> { private static final Log LOG = LogFactory.getLog(IterablePaging.class); private static final boolean DISABLED = true; private final DeepPagingCache _deepPagingCache; private final IndexSearcherCloseable _searcher; private final Query _query; private final AtomicBoolean _running; private final int _numHitsToCollect; private final boolean _runSlow; private final Sort _sort; private final DeepPageKey _key; private TotalHitsRef _totalHitsRef; private ProgressRef _progressRef; private int skipTo; private int gather = -1; public IterablePaging(AtomicBoolean running, IndexSearcherCloseable searcher, Query query, int numHitsToCollect, TotalHitsRef totalHitsRef, ProgressRef progressRef, boolean runSlow, Sort sort, DeepPagingCache deepPagingCache) throws BlurException { _deepPagingCache = deepPagingCache; _running = running; _sort = sort; try { _query = searcher.rewrite(query); } catch (IOException e) { throw new BException("Unknown error during rewrite", e); } _searcher = searcher; _numHitsToCollect = numHitsToCollect; _totalHitsRef = totalHitsRef == null ? new TotalHitsRef() : totalHitsRef; _progressRef = progressRef == null ? new ProgressRef() : progressRef; _runSlow = runSlow; if (DISABLED) { _key = null; } else { _key = new DeepPageKey(_query, _sort, _searcher.getIndexReader().getCombinedCoreAndDeletesKey()); } } public static class TotalHitsRef { // This is an atomic integer because more than likely if there is // any status sent to the user, it will be done in another thread. protected AtomicInteger totalHits = new AtomicInteger(0); public int totalHits() { return totalHits.get(); } } public static class ProgressRef { // These are atomic integers because more than likely if there is // any status sent to the user, it will be done in another thread. protected AtomicInteger skipTo = new AtomicInteger(0); protected AtomicInteger currentHitPosition = new AtomicInteger(0); protected AtomicInteger searchesPerformed = new AtomicInteger(0); protected AtomicLong queryTime = new AtomicLong(0); public int skipTo() { return skipTo.get(); } public int currentHitPosition() { return currentHitPosition.get(); } public int searchesPerformed() { return searchesPerformed.get(); } public long queryTime() { return queryTime.get(); } } /** * Gets the total hits of the search. * * @return the total hits. */ public int getTotalHits() { return _totalHitsRef.totalHits(); } /** * Allows for gathering of the total hits of this search. * * @param ref * {@link TotalHitsRef}. * @return this. */ public IterablePaging totalHits(TotalHitsRef ref) { _totalHitsRef = ref; return this; } /** * Skips the first x number of hits. * * @param skipTo * the number hits to skip. * @return this. */ public IterablePaging skipTo(int skipTo) { this.skipTo = skipTo; return this; } /** * Only gather up to x number of hits. * * @param gather * the number of hits to gather. * @return this. */ public IterablePaging gather(int gather) { this.gather = gather; return this; } /** * Allows for gathering the progress of the paging. * * @param ref * the {@link ProgressRef}. * @return this. */ public IterablePaging progress(ProgressRef ref) { this._progressRef = ref; return this; } /** * The {@link ScoreDoc} iterator. * * @throws BlurException */ @Override public BlurIterator<ScoreDoc, BlurException> iterator() throws BlurException { PagingIterator iterator = new PagingIterator(); DeepPageContainer deepPageContainer = getDeepPageContainer(skipTo); if (deepPageContainer == null) { deepPageContainer = new DeepPageContainer(); } iterator.after = deepPageContainer.scoreDoc; iterator.counter = deepPageContainer.position; iterator.search(); _progressRef.skipTo.set(skipTo); if (skipTo - deepPageContainer.position != 0) { LOG.warn("Skipping [{0}], Key [{1}] was missing, having to execute extra searches.", skipTo - deepPageContainer.position, _key); } for (int i = deepPageContainer.position; i < skipTo && iterator.hasNext(); i++) { // eats the hits, and moves the iterator to the desired skip to position. _progressRef.currentHitPosition.set(i); iterator.next(); } return iterator; } private DeepPageContainer getDeepPageContainer(int skipTo) { if (DISABLED) { return null; } return _deepPagingCache.lookup(_key, skipTo); } class PagingIterator implements BlurIterator<ScoreDoc, BlurException> { private static final String STOP_EXECUTION_COLLECTOR_EXCEPTION = "StopExecutionCollectorException"; private ScoreDoc[] scoreDocs; private int counter = 0; private int offset = 0; private int endPosition = gather == -1 ? Integer.MAX_VALUE : skipTo + gather; ScoreDoc after; void search() throws BlurException { long s = System.currentTimeMillis(); _progressRef.searchesPerformed.incrementAndGet(); try { TopDocCollectorInterface collector; if (_sort == null) { collector = BlurScoreDocCollector.create(_numHitsToCollect, after, _runSlow, _running); } else { collector = BlurFieldCollector.create(_sort, _numHitsToCollect, (FieldDoc) after, _runSlow, _running); } _searcher.search(_query, (Collector) collector); _totalHitsRef.totalHits.set(collector.getTotalHits()); TopDocs topDocs = collector.topDocs(); scoreDocs = topDocs.scoreDocs; } catch (StopExecutionCollectorException e) { throw new BlurException(STOP_EXECUTION_COLLECTOR_EXCEPTION, null, ErrorType.UNKNOWN); } catch (ExitingReaderException e) { throw new BlurException(STOP_EXECUTION_COLLECTOR_EXCEPTION, null, ErrorType.UNKNOWN); } catch (IOException e) { throw new BException("Unknown error during search call", e); } if (scoreDocs.length > 0) { after = scoreDocs[scoreDocs.length - 1]; addLastScoreDoc(); } else { after = null; } long e = System.currentTimeMillis(); _progressRef.queryTime.addAndGet(e - s); } private void addLastScoreDoc() { if (DISABLED) { return; } DeepPageContainer deepPageContainer = new DeepPageContainer(); deepPageContainer.scoreDoc = after; deepPageContainer.position = counter + scoreDocs.length; _deepPagingCache.add(_key, deepPageContainer); } @Override public boolean hasNext() { return counter < _totalHitsRef.totalHits() && counter < endPosition ? true : false; } @Override public ScoreDoc next() throws BlurException { if (isCurrentCollectorExhausted()) { search(); offset = 0; } _progressRef.currentHitPosition.set(counter); counter++; return scoreDocs[offset++]; } private boolean isCurrentCollectorExhausted() { return offset < scoreDocs.length ? false : true; } @Override public long getPosition() throws BlurException { return counter; } } }