All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.conquiris.search.AbstractSearcher Maven / Gradle / Ivy

/*
 * Copyright (C) the original author or authors.
 *
 * Licensed 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.
 */
package net.conquiris.search;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;

import net.conquiris.api.search.CountResult;
import net.conquiris.api.search.Highlight;
import net.conquiris.api.search.Highlight.HighlightedQuery;
import net.conquiris.api.search.HitMapper;
import net.conquiris.api.search.IndexNotAvailableException;
import net.conquiris.api.search.ItemResult;
import net.conquiris.api.search.PageResult;
import net.conquiris.api.search.SearchException;
import net.conquiris.api.search.Searcher;
import net.conquiris.lucene.search.Hit;
import net.conquiris.lucene.search.ScoredTotalHitCountCollector;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopFieldDocs;
import org.apache.lucene.search.TotalHitCountCollector;

import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;

/**
 * Abstract searcher implementation.
 * @author Andres Rodriguez
 */

abstract class AbstractSearcher implements Searcher {
	/** Constructor. */
	AbstractSearcher() {
	}

	/**
	 * Fetch searcher to use.
	 * @throws IndexNotAvailableException if an error occurs.
	 */
	abstract IndexSearcher getIndexSearcher();

	/**
	 * Dispose used searcher.
	 * @param searcher Searcher to dispose.
	 */
	abstract void disposeIndexSearcher(IndexSearcher searcher);

	/**
	 * Performs a primitive operation.
	 * @param operation Operation to perform.
	 * @return Operation return value.
	 */
	 T perform(Op operation) {
		final IndexSearcher searcher = getIndexSearcher();
		try {
			return operation.apply(searcher);
		} finally {
			disposeIndexSearcher(searcher);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see net.conquiris.api.search.Searcher#doc(int)
	 */
	public final Document doc(final int i) {
		return perform(new Op() {
			@Override
			Document perform(IndexSearcher searcher) throws Exception {
				return searcher.doc(i);
			}
		});
	}

	/*
	 * (non-Javadoc)
	 * @see net.conquiris.api.search.Searcher#rewrite(org.apache.lucene.search.Query)
	 */
	public final Query rewrite(final Query query) {
		return perform(new Op() {
			@Override
			Query perform(IndexSearcher searcher) throws Exception {
				return searcher.rewrite(query);
			}
		});
	}

	/*
	 * (non-Javadoc)
	 * @see net.conquiris.api.search.Searcher#search(org.apache.lucene.search.Query,
	 * org.apache.lucene.search.Filter, org.apache.lucene.search.Collector)
	 */
	public final void search(final Query query, final Filter filter, final Collector results) {
		perform(new Op() {
			@Override
			Object perform(IndexSearcher searcher) throws Exception {
				searcher.search(query, filter, results);
				return null;
			}
		});
	}

	/*
	 * (non-Javadoc)
	 * @see net.conquiris.api.search.Searcher#search(org.apache.lucene.search.Query,
	 * org.apache.lucene.search.Filter, int)
	 */
	public final TopDocs search(final Query query, final Filter filter, final int n) {
		return perform(new Op() {
			@Override
			TopDocs perform(IndexSearcher searcher) throws Exception {
				return searcher.search(query, filter, n);
			}
		});
	}

	public final TopFieldDocs search(final Query query, final Filter filter, final int n, final Sort sort) {
		return perform(new Op() {
			@Override
			TopFieldDocs perform(IndexSearcher searcher) throws Exception {
				return searcher.search(query, filter, n, sort);
			}
		});
	}

	/** TopDocs helper method. */
	private TopDocs getTopDocs(IndexSearcher searcher, Query query, Filter filter, Sort sort, int hits)
			throws IOException {
		final TopDocs docs;
		if (sort == null) {
			docs = searcher.search(query, filter, hits);
		} else {
			docs = searcher.search(query, filter, hits, sort);
		}
		return docs;
	}

	/*
	 * (non-Javadoc)
	 * @see net.conquiris.api.search.Searcher#getFirst(net.conquiris.api.search.DocMapper,
	 * org.apache.lucene.search.Query, org.apache.lucene.search.Filter, org.apache.lucene.search.Sort,
	 * net.conquiris.api.search.Highlight)
	 */
	public final  ItemResult getFirst(final HitMapper mapper, final Query query, final @Nullable Filter filter,
			final @Nullable Sort sort, final @Nullable Highlight highlight) {
		return perform(new Op>() {
			public ItemResult perform(IndexSearcher searcher) throws Exception {
				Stopwatch w = Stopwatch.createStarted();
				Query rewritten = searcher.rewrite(query);
				TopDocs docs = getTopDocs(searcher, query, filter, sort, 1);
				if (docs.totalHits > 0) {
					ScoreDoc sd = docs.scoreDocs[0];
					HighlightedQuery highlighted = MoreObjects.firstNonNull(highlight, Highlight.no()).highlight(rewritten);
					float score = sd.score;
					T item = map(searcher, sd, highlighted, mapper);
					return ItemResult.found(docs.totalHits, score, w.elapsed(TimeUnit.MILLISECONDS), item);
				} else {
					return ItemResult.notFound(w.elapsed(TimeUnit.MILLISECONDS));
				}
			}
		});
	}

	/*
	 * (non-Javadoc)
	 * @see net.conquiris.api.search.Searcher#getPage(net.conquiris.api.search.DocMapper,
	 * org.apache.lucene.search.Query, int, int, org.apache.lucene.search.Filter,
	 * org.apache.lucene.search.Sort, net.conquiris.api.search.Highlight)
	 */
	public final  PageResult getPage(final HitMapper mapper, final Query query, final int firstRecord,
			final int maxRecords, final @Nullable Filter filter, final @Nullable Sort sort,
			final @Nullable Highlight highlight) {

		// Corner case
		if (maxRecords < 1) {
			CountResult r = getCount(query, filter, true);
			return PageResult.notFound(r.getTotalHits(), r.getMaxScore(), r.getTime(), firstRecord);
		}

		// Normal operation
		return perform(new Op>() {
			public PageResult perform(IndexSearcher searcher) throws Exception {
				Stopwatch w = Stopwatch.createStarted();
				int total = firstRecord + maxRecords;
				Query rewritten = searcher.rewrite(query);
				TopDocs docs = getTopDocs(searcher, rewritten, filter, sort, total);
				if (docs.totalHits > 0) {
					int n = Math.min(total, docs.scoreDocs.length);
					float score = docs.getMaxScore();
					if (n > firstRecord) {
						final List items = Lists.newArrayListWithCapacity(n - firstRecord);
						HighlightedQuery highlighted = MoreObjects.firstNonNull(highlight, Highlight.no()).highlight(rewritten);
						for (int i = firstRecord; i < n; i++) {
							ScoreDoc sd = docs.scoreDocs[i];
							T item = map(searcher, sd, highlighted, mapper);
							items.add(item);
						}
						return PageResult.found(docs.totalHits, score, w.elapsed(TimeUnit.MILLISECONDS), firstRecord, items);
					} else {
						return PageResult.notFound(docs.totalHits, score, w.elapsed(TimeUnit.MILLISECONDS), firstRecord);
					}
				} else {
					return PageResult.notFound(w.elapsed(TimeUnit.MILLISECONDS), firstRecord);
				}
			}
		});
	}

	/*
	 * (non-Javadoc)
	 * @see net.conquiris.api.search.Searcher#getCount(org.apache.lucene.search.Query,
	 * org.apache.lucene.search.Filter, boolean)
	 */
	@Override
	public CountResult getCount(final Query query, final @Nullable Filter filter, final boolean score) {
		return perform(new Op() {
			public CountResult perform(IndexSearcher searcher) throws Exception {
				final Stopwatch w = Stopwatch.createStarted();
				final ScoredTotalHitCountCollector scoredCollector;
				final TotalHitCountCollector collector;
				if (score) {
					scoredCollector = new ScoredTotalHitCountCollector();
					collector = scoredCollector;
				} else {
					scoredCollector = null;
					collector = new TotalHitCountCollector();
				}
				searcher.search(query, filter, collector);
				final float maxScore = score ? scoredCollector.getMaxScore() : 1.0f;
				return CountResult.of(collector.getTotalHits(), maxScore, w.elapsed(TimeUnit.MILLISECONDS));
			}
		});
	}

	/**
	 * Searcher primitive operation.
	 * @param  Return type.
	 */
	abstract class Op implements Function {
		@Override
		public final T apply(IndexSearcher searcher) {
			try {
				return perform(searcher);
			} catch (SearchException e) {
				throw e;
			} catch (Exception e) {
				throw new IndexNotAvailableException(e);
			}
		}

		final  H map(IndexSearcher searcher, ScoreDoc sd, HighlightedQuery q, HitMapper mapper) throws Exception {
			final int id = sd.doc;
			final float score = sd.score;
			final Document doc;
			FieldSelector selector = mapper.getFieldSelector();
			if (selector == null) {
				doc = searcher.doc(id);
			} else {
				doc = searcher.doc(id, selector);
			}
			final Hit hit = Hit.of(id, score, doc, q.getFragments(doc));
			return mapper.apply(hit);
		}

		abstract T perform(IndexSearcher searcher) throws Exception;
	}

}