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

org.hibernate.search.query.engine.impl.QueryHits Maven / Gradle / Ivy

There is a newer version: 5.11.12.Final
Show newest version
/*
 * Hibernate Search, full-text search for your domain model
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later
 * See the lgpl.txt file in the root directory or .
 */
package org.hibernate.search.query.engine.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.StoredFieldVisitor;
import org.apache.lucene.search.Collector;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TimeLimitingCollector;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopDocsCollector;
import org.apache.lucene.search.TopFieldCollector;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.search.TotalHitCountCollector;
import org.apache.lucene.util.Counter;

import org.hibernate.search.exception.SearchException;
import org.hibernate.search.query.collector.impl.FacetCollector;
import org.hibernate.search.query.collector.impl.FieldCacheCollector;
import org.hibernate.search.query.collector.impl.FieldCacheCollectorFactory;
import org.hibernate.search.query.dsl.impl.FacetingRequestImpl;
import org.hibernate.search.query.engine.spi.TimeoutExceptionFactory;
import org.hibernate.search.query.engine.spi.TimeoutManager;
import org.hibernate.search.query.facet.Facet;
import org.hibernate.search.spatial.Coordinates;
import org.hibernate.search.spatial.impl.DistanceCollector;

/**
 * A helper class which gives access to the current query and its hits. This class will dynamically
 * reload the underlying {@code TopDocs} if required.
 *
 * @author Hardy Ferentschik
 * @author Sanne Grinovero  (C) 2011 Red Hat Inc.
 */
public class QueryHits {

	private static final int DEFAULT_TOP_DOC_RETRIEVAL_SIZE = 100;

	private final LazyQueryState searcher;
	private final Filter filter;
	private final Sort sort;
	private final Map facetRequests;
	private final TimeoutManagerImpl timeoutManager;

	private int totalHits;
	private TopDocs topDocs;
	private Map> facetMap;
	private List facetCollectors;
	private DistanceCollector distanceCollector = null;

	private final boolean enableFieldCacheOnClassName;

	private Coordinates spatialSearchCenter = null;
	private String spatialFieldName = null;

	/**
	 * If enabled, after hits collection it will contain the class name for each hit
	 */
	private FieldCacheCollector classTypeCollector;

	/**
	 * If enabled, a Collector will collect values from the primary keys
	 */
	private final FieldCacheCollectorFactory idFieldCollectorFactory;
	private FieldCacheCollector idFieldCollector;

	private final TimeoutExceptionFactory timeoutExceptionFactory;

	public QueryHits(LazyQueryState searcher,
					Filter filter,
					Sort sort,
					TimeoutManagerImpl timeoutManager,
					Map facetRequests,
					boolean enableFieldCacheOnTypes,
					FieldCacheCollectorFactory idFieldCollector,
					TimeoutExceptionFactory timeoutExceptionFactory,
					Coordinates spatialSearchCenter,
					String spatialFieldName)
			throws IOException {
		this(
				searcher, filter, sort, DEFAULT_TOP_DOC_RETRIEVAL_SIZE, timeoutManager, facetRequests,
				enableFieldCacheOnTypes, idFieldCollector, timeoutExceptionFactory, spatialSearchCenter, spatialFieldName
		);
	}

	public QueryHits(LazyQueryState searcher,
					Filter filter,
					Sort sort,
					Integer n,
					TimeoutManagerImpl timeoutManager,
					Map facetRequests,
					boolean enableFieldCacheOnTypes,
					FieldCacheCollectorFactory idFieldCollector,
					TimeoutExceptionFactory timeoutExceptionFactory,
					Coordinates spatialSearchCenter,
					String spatialFieldName)
			throws IOException {
		this.timeoutManager = timeoutManager;
		this.searcher = searcher;
		this.filter = filter;
		this.sort = sort;
		this.facetRequests = facetRequests;
		this.enableFieldCacheOnClassName = enableFieldCacheOnTypes;
		this.idFieldCollectorFactory = idFieldCollector;
		this.timeoutExceptionFactory = timeoutExceptionFactory;
		this.spatialSearchCenter = spatialSearchCenter;
		this.spatialFieldName = spatialFieldName;
		updateTopDocs( n );
	}

	public Document doc(int index) throws IOException {
		return searcher.doc( docId( index ) );
	}

	/**
	 * This document loading strategy doesn't return anything as it's the responsibility
	 * of the passed StoredFieldVisitor instance to collect the data it needs.
	 * @param index
	 * @param fieldVisitor
	 * @throws IOException
	 */
	public void visitDocument(int index, StoredFieldVisitor fieldVisitor) throws IOException {
		searcher.doc( docId( index ), fieldVisitor );
	}

	public ScoreDoc scoreDoc(int index) throws IOException {
		if ( index >= totalHits ) {
			throw new SearchException( "Not a valid ScoreDoc index: " + index );
		}

		// TODO - Is there a better way to get more TopDocs? Get more or less?
		if ( index >= topDocs.scoreDocs.length ) {
			updateTopDocs( 2 * index );
		}
		//if the refresh timed out, raise an exception
		if ( timeoutManager.isTimedOut() && index >= topDocs.scoreDocs.length ) {
			throw timeoutExceptionFactory.createTimeoutException(
					"Timeout period exceeded. Cannot load document: " + index,
					searcher.describeQuery()
			);
		}
		return topDocs.scoreDocs[index];
	}

	public int docId(int index) throws IOException {
		return scoreDoc( index ).doc;
	}

	public float score(int index) throws IOException {
		return scoreDoc( index ).score;
	}

	public Double spatialDistance(int index) throws IOException {
		if ( spatialSearchCenter == null ) {
			return null;
		}
		return Double.valueOf( distanceCollector.getDistance( docId( index ) ) );
	}

	public Explanation explain(int index) throws IOException {
		final Explanation explanation = searcher.explain( docId( index ) );
		timeoutManager.isTimedOut();
		return explanation;
	}

	public int getTotalHits() {
		return totalHits;
	}

	public TopDocs getTopDocs() {
		return topDocs;
	}

	public Map> getFacets() {
		if ( facetRequests == null || facetRequests.size() == 0 ) {
			return Collections.emptyMap();
		}
		return facetMap;
	}

	/**
	 * @param n the number of {@code TopDoc}s to retrieve. The actual retrieved number of {@code TopDoc}s is n or the
	 * total number of documents if {@code n > maxDoc}
	 *
	 * @throws IOException in case a search exception occurs
	 */
	private void updateTopDocs(int n) throws IOException {
		final int totalMaxDocs = searcher.maxDoc();
		final int maxDocs = Math.min( n, totalMaxDocs );

		final TopDocsCollector topDocCollector;
		final TotalHitCountCollector hitCountCollector;
		Collector collector = null;
		if ( maxDocs != 0 ) {
			topDocCollector = createTopDocCollector( maxDocs );
			hitCountCollector = null;
			collector = topDocCollector;
			collector = optionallyEnableFieldCacheOnTypes( collector, totalMaxDocs, maxDocs );
			collector = optionallyEnableFieldCacheOnIds( collector, totalMaxDocs, maxDocs );
			collector = optionallyEnableFacetingCollectors( collector );
			collector = optionallyEnableDistanceCollector( collector, maxDocs );
		}
		else {
			topDocCollector = null;
			hitCountCollector = new TotalHitCountCollector();
			collector = hitCountCollector;
		}
		collector = decorateWithTimeOutCollector( collector );

		boolean timeoutNow = isImmediateTimeout();
		if ( !timeoutNow ) {
			try {
				searcher.search( filter, collector );
			}
			catch (TimeLimitingCollector.TimeExceededException e) {
				//we have reached the time limit and stopped before the end
				//TimeoutManager.isTimedOut should be above that limit but set if for safety
				timeoutManager.forceTimedOut();
			}
		}

		// update top docs and totalHits
		if ( maxDocs != 0 ) {
			this.topDocs = topDocCollector.topDocs();
			this.totalHits = topDocs.totalHits;
			// if we were collecting facet data we have to update our instance state
			if ( facetCollectors != null && !facetCollectors.isEmpty() ) {
				facetMap = new HashMap>();
				for ( FacetCollector facetCollector : facetCollectors ) {
					facetMap.put( facetCollector.getFacetName(), facetCollector.getFacetList() );
				}
			}
		}
		else {
			this.topDocs = null;
			this.totalHits = hitCountCollector.getTotalHits();
		}
		timeoutManager.isTimedOut();
	}

	private Collector optionallyEnableFacetingCollectors(Collector collector) {
		if ( facetRequests == null || facetRequests.isEmpty() ) {
			return collector;
		}
		facetCollectors = new ArrayList();
		Collector nextInChain = collector;
		for ( FacetingRequestImpl entry : facetRequests.values() ) {
			FacetCollector facetCollector = new FacetCollector( nextInChain, entry );
			nextInChain = facetCollector;
			facetCollectors.add( facetCollector );
		}

		return facetCollectors.get( facetCollectors.size() - 1 );
	}

	private Collector optionallyEnableDistanceCollector(Collector collector, int maxDocs) {
		if ( spatialFieldName == null || spatialFieldName.isEmpty() || spatialSearchCenter == null ) {
			return collector;
		}
		distanceCollector = new DistanceCollector( collector, spatialSearchCenter, maxDocs, spatialFieldName );

		return distanceCollector;
	}

	private boolean isImmediateTimeout() {
		boolean timeoutAt0 = false;
		if ( timeoutManager.getType() == TimeoutManager.Type.LIMIT ) {
			final Long timeoutLeft = timeoutManager.getTimeoutLeftInMilliseconds();
			if ( timeoutLeft != null ) {
				if ( timeoutLeft == 0l ) {
					if ( timeoutManager.getType() == TimeoutManager.Type.LIMIT && timeoutManager.isTimedOut() ) {
						timeoutManager.forceTimedOut();
						timeoutAt0 = true;
					}
				}
			}
			else {
				if ( timeoutManager.isTimedOut() ) {
					timeoutManager.forceTimedOut();
				}
			}
		}
		return timeoutAt0;
	}

	private Collector decorateWithTimeOutCollector(Collector collector) {
		Collector maybeTimeLimitingCollector = collector;
		if ( timeoutManager.getType() == TimeoutManager.Type.LIMIT ) {
			final Long timeoutLeft = timeoutManager.getTimeoutLeftInMilliseconds();
			if ( timeoutLeft != null ) {
				Counter counter = timeoutManager.getLuceneTimeoutCounter();
				maybeTimeLimitingCollector = new TimeLimitingCollector( collector, counter, timeoutLeft);
			}
		}
		return maybeTimeLimitingCollector;
	}

	private TopDocsCollector createTopDocCollector(int maxDocs) throws IOException {
		TopDocsCollector topCollector;
		if ( sort == null ) {
			topCollector = TopScoreDocCollector.create( maxDocs, !searcher.scoresDocsOutOfOrder() );
		}
		else {
			boolean fillFields = true;
			topCollector = TopFieldCollector.create(
					sort,
					maxDocs,
					fillFields,
					searcher.isFieldSortDoTrackScores(),
					searcher.isFieldSortDoMaxScore(),
					!searcher.scoresDocsOutOfOrder()
			);
		}
		return topCollector;
	}

	private Collector optionallyEnableFieldCacheOnIds(Collector collector, int totalMaxDocs, int maxDocs) {
		if ( idFieldCollectorFactory != null ) {
			idFieldCollector = idFieldCollectorFactory.createFieldCollector( collector, totalMaxDocs, maxDocs );
			return idFieldCollector;
		}
		return collector;
	}

	private Collector optionallyEnableFieldCacheOnTypes(Collector collector, int totalMaxDocs, int expectedMatchesCount) {
		if ( enableFieldCacheOnClassName ) {
			classTypeCollector = FieldCacheCollectorFactory
					.CLASS_TYPE_FIELD_CACHE_COLLECTOR_FACTORY
					.createFieldCollector( collector, totalMaxDocs, expectedMatchesCount );
			return classTypeCollector;
		}
		else {
			return collector;
		}
	}

	public FieldCacheCollector getClassTypeCollector() {
		return classTypeCollector;
	}

	public FieldCacheCollector getIdsCollector() {
		return idFieldCollector;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy