Package org.hibernate.search.query.engine.impl

Source Code of org.hibernate.search.query.engine.impl.HSQueryImpl

/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* JBoss, Home of Professional Open Source
* Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA  02110-1301, USA.
*/
package org.hibernate.search.query.engine.impl;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TermQuery;

import org.hibernate.annotations.common.AssertionFailure;
import org.hibernate.search.FullTextFilter;
import org.hibernate.search.ProjectionConstants;
import org.hibernate.search.SearchException;
import org.hibernate.search.annotations.FieldCacheType;
import org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity;
import org.hibernate.search.engine.spi.EntityIndexBinder;
import org.hibernate.search.engine.impl.FilterDef;
import org.hibernate.search.engine.spi.SearchFactoryImplementor;
import org.hibernate.search.filter.StandardFilterKey;
import org.hibernate.search.filter.impl.ChainedFilter;
import org.hibernate.search.filter.FilterKey;
import org.hibernate.search.filter.FullTextFilterImplementor;
import org.hibernate.search.filter.ShardSensitiveOnlyFilter;
import org.hibernate.search.filter.impl.CachingWrapperFilter;
import org.hibernate.search.filter.impl.FullTextFilterImpl;
import org.hibernate.search.indexes.spi.IndexManager;
import org.hibernate.search.query.collector.impl.FieldCacheCollectorFactory;
import org.hibernate.search.query.engine.spi.DocumentExtractor;
import org.hibernate.search.query.engine.spi.EntityInfo;
import org.hibernate.search.query.engine.spi.HSQuery;
import org.hibernate.search.query.engine.spi.TimeoutExceptionFactory;
import org.hibernate.search.query.engine.spi.TimeoutManager;
import org.hibernate.search.reader.impl.MultiReaderFactory;
import org.hibernate.search.store.IndexShardingStrategy;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;

import static org.hibernate.search.util.impl.CollectionHelper.newHashMap;
import static org.hibernate.search.util.impl.FilterCacheModeTypeHelper.cacheInstance;
import static org.hibernate.search.util.impl.FilterCacheModeTypeHelper.cacheResults;

/**
* @author Emmanuel Bernard <emmanuel@hibernate.org>
* @author Hardy Ferentschik <hardy@hibernate.org>
*/
public class HSQueryImpl implements HSQuery, Serializable {
  private static final Log log = LoggerFactory.make();
  private static final FullTextFilterImplementor[] EMPTY_FULL_TEXT_FILTER_IMPLEMENTOR = new FullTextFilterImplementor[0];

  private transient SearchFactoryImplementor searchFactoryImplementor;
  private Query luceneQuery;
  private List<Class<?>> targetedEntities;
  private transient TimeoutManagerImpl timeoutManager;
  private Set<Class<?>> indexedTargetedEntities;
  private boolean allowFieldSelectionInProjection = true;

  /**
   * The  map of currently active/enabled filters.
   */
  private final Map<String, FullTextFilterImpl> filterDefinitions = newHashMap();

  /**
   * Combined chained filter to be applied to the query.
   */
  private Filter filter;

  /**
   * User specified filters. Will be combined into a single chained filter {@link #filter}.
   */
  private Filter userFilter;
  private Sort sort;
  private String[] projectedFields;
  private int firstResult;
  private Integer maxResults;
  private transient Set<Class<?>> classesAndSubclasses;
  //optimization: if we can avoid the filter clause (we can most of the time) do it as it has a significant perf impact
  private boolean needClassFilterClause;
  private Set<String> idFieldNames;
  private boolean useFieldCacheOnClassTypes = false;
  private transient FacetManagerImpl facetManager;
  private transient TimeoutExceptionFactory timeoutExceptionFactory;

  /**
   * The number of results for this query. This field gets populated once {@link #queryResultSize}, {@link #queryEntityInfos}
   * or {@link #queryDocumentExtractor} is called.
   */
  private Integer resultSize;


  public HSQueryImpl(SearchFactoryImplementor searchFactoryImplementor) {
    this.searchFactoryImplementor = searchFactoryImplementor;
    this.timeoutExceptionFactory = searchFactoryImplementor.getDefaultTimeoutExceptionFactory();
  }
 
  public void afterDeserialise(SearchFactoryImplementor searchFactoryImplementor) {
    this.searchFactoryImplementor = searchFactoryImplementor;
  }

  public HSQuery luceneQuery(Query query) {
    clearCachedResults();
    this.luceneQuery = query;
    return this;
  }

  public HSQuery targetedEntities(List<Class<?>> classes) {
    clearCachedResults();
    this.targetedEntities = classes == null ? new ArrayList<Class<?>>( 0 ) : new ArrayList<Class<?>>( classes );
    final Class[] classesAsArray = targetedEntities.toArray( new Class[targetedEntities.size()] );
    this.indexedTargetedEntities = searchFactoryImplementor.getIndexedTypesPolymorphic( classesAsArray );
    if ( targetedEntities != null && targetedEntities.size() > 0 && indexedTargetedEntities.size() == 0 ) {
      String msg = "None of the specified entity types or any of their subclasses are indexed.";
      throw new IllegalArgumentException( msg );
    }
    return this;
  }

  public HSQuery sort(Sort sort) {
    this.sort = sort;
    return this;
  }

  public HSQuery filter(Filter filter) {
    clearCachedResults();
    this.userFilter = filter;
    return this;
  }

  public HSQuery timeoutExceptionFactory(TimeoutExceptionFactory exceptionFactory) {
    this.timeoutExceptionFactory = exceptionFactory;
    return this;
  }

  public HSQuery projection(String... fields) {
    if ( fields == null || fields.length == 0 ) {
      this.projectedFields = null;
    }
    else {
      this.projectedFields = fields;
    }
    return this;
  }

  public HSQuery firstResult(int firstResult) {
    if ( firstResult < 0 ) {
      throw new IllegalArgumentException( "'first' pagination parameter less than 0" );
    }
    this.firstResult = firstResult;
    return this;
  }

  public HSQuery maxResults(Integer maxResults) {
    if ( maxResults != null && maxResults < 0 ) {
      throw new IllegalArgumentException( "'max' pagination parameter less than 0" );
    }
    this.maxResults = maxResults;
    return this;
  }

  /**
   * List of targeted entities as described by the user
   */
  public List<Class<?>> getTargetedEntities() {
    return targetedEntities;
  }

  /**
   * Set of indexed entities corresponding to the class hierarchy of the targeted entities
   */
  public Set<Class<?>> getIndexedTargetedEntities() {
    return indexedTargetedEntities;
  }

  public String[] getProjectedFields() {
    return projectedFields;
  }

  private TimeoutManagerImpl getTimeoutManagerImpl() {
    if ( timeoutManager == null ) {
      if ( luceneQuery == null ) {
        throw new AssertionFailure( "Requesting TimeoutManager before setting luceneQuery()" );
      }
      timeoutManager = new TimeoutManagerImpl( luceneQuery, timeoutExceptionFactory );
    }
    return timeoutManager;
  }

  public TimeoutManager getTimeoutManager() {
    return getTimeoutManagerImpl();
  }

  public FacetManagerImpl getFacetManager() {
    if ( facetManager == null ) {
      facetManager = new FacetManagerImpl( this );
    }
    return facetManager;
  }

  public Query getLuceneQuery() {
    return luceneQuery;
  }

  public List<EntityInfo> queryEntityInfos() {
    IndexSearcherWithPayload searcher = buildSearcher();
    if ( searcher == null ) {
      return Collections.emptyList();
    }
    try {
      QueryHits queryHits = getQueryHits( searcher, calculateTopDocsRetrievalSize() );
      int first = getFirstResultIndex();
      int max = max( first, queryHits.getTotalHits() );

      int size = max - first + 1 < 0 ? 0 : max - first + 1;
      List<EntityInfo> infos = new ArrayList<EntityInfo>( size );
      DocumentExtractor extractor = buildDocumentExtractor( searcher, queryHits, first, max );
      for ( int index = first; index <= max; index++ ) {
        infos.add( extractor.extract( index ) );
        //TODO should we measure on each extractor?
        if ( index % 10 == 0 ) {
          getTimeoutManager().isTimedOut();
        }
      }
      return infos;
    }
    catch ( IOException e ) {
      throw new SearchException( "Unable to query Lucene index", e );
    }
    finally {
      closeSearcher( searcher );
    }
  }

  private DocumentExtractor buildDocumentExtractor(IndexSearcherWithPayload searcher, QueryHits queryHits, int first, int max) {
    return new DocumentExtractorImpl(
        queryHits,
        searchFactoryImplementor,
        projectedFields,
        idFieldNames,
        allowFieldSelectionInProjection,
        searcher,
        luceneQuery,
        first,
        max,
        classesAndSubclasses
    );
  }

  /**
   * DocumentExtractor returns a traverser over the full-text results (EntityInfo)
   * This operation is lazy bound:
   * - the query is executed
   * - results are not retrieved until actually requested
   *
   * DocumentExtractor objects *must* be closed when the results are no longer traversed.
   */
  public DocumentExtractor queryDocumentExtractor() {
    //keep the searcher open until the resultset is closed
    //find the directories
    IndexSearcherWithPayload openSearcher = buildSearcher();
    //FIXME: handle null searcher
    try {
      QueryHits queryHits = getQueryHits( openSearcher, calculateTopDocsRetrievalSize() );
      int first = getFirstResultIndex();
      int max = max( first, queryHits.getTotalHits() );
      return buildDocumentExtractor( openSearcher, queryHits, first, max );
    }
    catch ( IOException e ) {
      closeSearcher( openSearcher );
      throw new SearchException( "Unable to query Lucene index", e );
    }
  }

  public int queryResultSize() {
    if ( resultSize == null ) {
      //the timeoutManager does not need to be stopped nor reset as a start does indeed reset
      getTimeoutManager().start();
      //get result size without object initialization
      IndexSearcherWithPayload searcher = buildSearcher( searchFactoryImplementor, false );
      if ( searcher == null ) {
        resultSize = 0;
      }
      else {
        try {
          QueryHits queryHits = getQueryHits( searcher, 0 );
          resultSize = queryHits.getTotalHits();
        }
        catch ( IOException e ) {
          throw new SearchException( "Unable to query Lucene index", e );
        }
        finally {
          closeSearcher( searcher );
        }
      }
    }
    return this.resultSize;
  }

  public Explanation explain(int documentId) {
    //don't use TimeoutManager here as explain is a dev tool when things are weird... or slow :)
    Explanation explanation = null;
    IndexSearcherWithPayload searcher = buildSearcher( searchFactoryImplementor, true );
    if ( searcher == null ) {
      throw new SearchException(
          "Unable to build explanation for document id:"
              + documentId + ". no index found"
      );
    }
    try {
      org.apache.lucene.search.Query filteredQuery = filterQueryByClasses( luceneQuery );
      buildFilters();
      explanation = searcher.getSearcher().explain( filteredQuery, documentId );
    }
    catch ( IOException e ) {
      throw new SearchException( "Unable to query Lucene index and build explanation", e );
    }
    finally {
      closeSearcher( searcher );
    }
    return explanation;
  }

  public FullTextFilter enableFullTextFilter(String name) {
    clearCachedResults();
    FullTextFilterImpl filterDefinition = filterDefinitions.get( name );
    if ( filterDefinition != null ) {
      return filterDefinition;
    }

    filterDefinition = new FullTextFilterImpl();
    filterDefinition.setName( name );
    FilterDef filterDef = searchFactoryImplementor.getFilterDefinition( name );
    if ( filterDef == null ) {
      throw log.unknownFullTextFilter( name );
    }
    filterDefinitions.put( name, filterDefinition );
    return filterDefinition;
  }

  public void disableFullTextFilter(String name) {
    clearCachedResults();
    filterDefinitions.remove( name );
  }

  private void closeSearcher(IndexSearcherWithPayload searcherWithPayload) {
    if ( searcherWithPayload == null ) {
      return;
    }
    searcherWithPayload.closeSearcher( luceneQuery, searchFactoryImplementor );
  }

  /**
   * This class caches some of the query results and we need to reset the state in case something in the query
   * changes (eg a new filter is set).
   */
  void clearCachedResults() {
    resultSize = null;
  }

  /**
   * Execute the lucene search and return the matching hits.
   *
   * @param searcher The index searcher.
   * @param n Number of documents to retrieve
   *
   * @return An instance of <code>QueryHits</code> wrapping the Lucene query and the matching documents.
   *
   * @throws IOException in case there is an error executing the lucene search.
   */
  private QueryHits getQueryHits(IndexSearcherWithPayload searcher, Integer n) throws IOException {
    org.apache.lucene.search.Query filteredQuery = filterQueryByClasses( luceneQuery );
    buildFilters();
    QueryHits queryHits;

    boolean stats = searchFactoryImplementor.getStatistics().isStatisticsEnabled();
    long startTime = 0;
    if ( stats ) {
      startTime = System.nanoTime();
    }

    if ( n == null ) { // try to make sure that we get the right amount of top docs
      queryHits = new QueryHits(
          searcher,
          filteredQuery,
          filter,
          sort,
          getTimeoutManagerImpl(),
          facetManager.getFacetRequests(),
          useFieldCacheOnTypes(),
          getAppropriateIdFieldCollectorFactory(),
          this.timeoutExceptionFactory
      );
    }
    else if ( 0 == n) {
      queryHits = new QueryHits(
          searcher,
          filteredQuery,
          filter,
          null,
          0,
          getTimeoutManagerImpl(),
          null,
          false,
          null,
          this.timeoutExceptionFactory
      );
    }
    else {
      queryHits = new QueryHits(
          searcher,
          filteredQuery,
          filter,
          sort,
          n,
          getTimeoutManagerImpl(),
          facetManager.getFacetRequests(),
          useFieldCacheOnTypes(),
          getAppropriateIdFieldCollectorFactory(),
          this.timeoutExceptionFactory
      );
    }
    resultSize = queryHits.getTotalHits();

    if ( stats ) {
      searchFactoryImplementor.getStatisticsImplementor()
          .searchExecuted( filteredQuery.toString(), System.nanoTime() - startTime );
    }
    facetManager.setFacetResults( queryHits.getFacets() );
    return queryHits;
  }

  /**
   * @return Calculates the number of <code>TopDocs</code> which should be retrieved as part of the query. If Hibernate's
   *         pagination parameters are set returned value is <code>first + maxResults</code>. Otherwise <code>null</code> is
   *         returned.
   */
  private Integer calculateTopDocsRetrievalSize() {
    if ( maxResults == null ) {
      return null;
    }
    else {
      long tmpMaxResult = (long) getFirstResultIndex() + maxResults;
      if ( tmpMaxResult >= Integer.MAX_VALUE ) {
        // don't return just Integer.MAX_VALUE due to a bug in Lucene - see HSEARCH-330
        return Integer.MAX_VALUE - 1;
      }
      if ( tmpMaxResult == 0 ) {
        return 1; // Lucene enforces that at least one top doc will be retrieved. See also HSEARCH-604
      }
      else {
        return (int) tmpMaxResult;
      }
    }
  }

  private int getFirstResultIndex() {
    return firstResult;
  }

  private IndexSearcherWithPayload buildSearcher() {
    return buildSearcher( searchFactoryImplementor, null );
  }

  /**
   * Build the index searcher for this fulltext query.
   *
   * @param searchFactoryImplementor the search factory.
   * @param forceScoring if true, force SCORE computation, if false, force not to compute score, if null used best choice
   *
   * @return the <code>IndexSearcher</code> for this query (can be <code>null</code>.
   *         TODO change classesAndSubclasses by side effect, which is a mismatch with the Searcher return, fix that.
   */
  private IndexSearcherWithPayload buildSearcher(SearchFactoryImplementor searchFactoryImplementor, Boolean forceScoring) {
    Map<Class<?>, EntityIndexBinder<?>> builders = searchFactoryImplementor.getIndexBindingForEntity();
    List<IndexManager> targetedIndexes = new ArrayList<IndexManager>();
    Set<String> idFieldNames = new HashSet<String>();

    Similarity searcherSimilarity = null;
    //TODO check if caching this work for the last n list of indexedTargetedEntities makes a perf boost
    if ( indexedTargetedEntities.size() == 0 ) {
      // empty indexedTargetedEntities array means search over all indexed entities,
      // but we have to make sure there is at least one
      if ( builders.isEmpty() ) {
        throw new SearchException(
            "There are no mapped entities. Don't forget to add @Indexed to at least one class."
        );
      }

      for ( EntityIndexBinder indexBinder : builders.values() ) {
        DocumentBuilderIndexedEntity<?> builder = indexBinder.getDocumentBuilder();
        searcherSimilarity = checkSimilarity( searcherSimilarity, builder );
        if ( builder.getIdKeywordName() != null ) {
          idFieldNames.add( builder.getIdKeywordName() );
          allowFieldSelectionInProjection = allowFieldSelectionInProjection && builder.allowFieldSelectionInProjection();
        }
        useFieldCacheOnClassTypes = useFieldCacheOnClassTypes || builder.getFieldCacheOption()
            .contains( FieldCacheType.CLASS );
        populateIndexManagers( targetedIndexes, indexBinder.getSelectionStrategy() );
      }
      classesAndSubclasses = null;
    }
    else {
      Set<Class<?>> involvedClasses = new HashSet<Class<?>>( indexedTargetedEntities.size() );
      involvedClasses.addAll( indexedTargetedEntities );
      for ( Class<?> clazz : indexedTargetedEntities ) {
        EntityIndexBinder<?> indexBinder = builders.get( clazz );
        if ( indexBinder != null ) {
          DocumentBuilderIndexedEntity<?> builder = indexBinder.getDocumentBuilder();
          involvedClasses.addAll( builder.getMappedSubclasses() );
        }
      }

      for ( Class clazz : involvedClasses ) {
        EntityIndexBinder indexBinder = builders.get( clazz );
        //TODO should we rather choose a polymorphic path and allow non mapped entities
        if ( indexBinder == null ) {
          throw new SearchException( "Not a mapped entity (don't forget to add @Indexed): " + clazz );
        }
        DocumentBuilderIndexedEntity<?> builder = indexBinder.getDocumentBuilder();
        if ( builder.getIdKeywordName() != null ) {
          idFieldNames.add( builder.getIdKeywordName() );
          allowFieldSelectionInProjection = allowFieldSelectionInProjection && builder.allowFieldSelectionInProjection();
        }
        searcherSimilarity = checkSimilarity( searcherSimilarity, builder );
        useFieldCacheOnClassTypes = useFieldCacheOnClassTypes || builder.getFieldCacheOption()
            .contains( FieldCacheType.CLASS );
        populateIndexManagers( targetedIndexes, indexBinder.getSelectionStrategy() );
      }
      this.classesAndSubclasses = involvedClasses;
    }
    this.idFieldNames = idFieldNames;

    //compute optimization needClassFilterClause
    //if at least one DP contains one class that is not part of the targeted classesAndSubclasses we can't optimize
    if ( classesAndSubclasses != null ) {
      for ( IndexManager indexManager : targetedIndexes ) {
        final Set<Class<?>> classesInIndexManager = indexManager.getContainedTypes();
        // if an IndexManager contains only one class, we know for sure it's part of classesAndSubclasses
        if ( classesInIndexManager.size() > 1 ) {
          //risk of needClassFilterClause
          for ( Class clazz : classesInIndexManager ) {
            if ( !classesAndSubclasses.contains( clazz ) ) {
              this.needClassFilterClause = true;
              break;
            }
          }
        }
        if ( this.needClassFilterClause ) {
          break;
        }
      }
    }
    else {
      Map<Class<?>, EntityIndexBinder<?>> documentBuildersIndexedEntities = searchFactoryImplementor.getIndexBindingForEntity();
      this.classesAndSubclasses = documentBuildersIndexedEntities.keySet();
    }

    //set up the searcher
    final IndexManager[] indexManagers = targetedIndexes.toArray(
        new IndexManager[targetedIndexes.size()]
    );
    IndexSearcher is = new IndexSearcher(
        MultiReaderFactory.openReader( indexManagers )
    );
    is.setSimilarity( searcherSimilarity );

    //handle the sort and projection
    final String[] projection = this.projectedFields;
    if ( Boolean.TRUE.equals( forceScoring ) ) {
      return new IndexSearcherWithPayload( is, true, true );
    }
    else if ( Boolean.FALSE.equals( forceScoring ) ) {
      return new IndexSearcherWithPayload( is, false, false );
    }
    else if ( this.sort != null && projection != null ) {
      boolean activate = false;
      for ( String field : projection ) {
        if ( SCORE.equals( field ) ) {
          activate = true;
          break;
        }
      }
      if ( activate ) {
        return new IndexSearcherWithPayload( is, true, false );
      }
    }
    //default
    return new IndexSearcherWithPayload( is, false, false );
  }

  private Similarity checkSimilarity(Similarity similarity, DocumentBuilderIndexedEntity builder) {
    if ( similarity == null ) {
      similarity = builder.getSimilarity();
    }
    else if ( !similarity.getClass().equals( builder.getSimilarity().getClass() ) ) {
      throw new SearchException(
          "Cannot perform search on two entities with differing Similarity implementations (" + similarity.getClass()
              .getName() + " & " + builder.getSimilarity().getClass().getName() + ")"
      );
    }

    return similarity;
  }

  private void populateIndexManagers(List<IndexManager> indexManagersTarget, final IndexShardingStrategy indexShardingStrategy) {
    final IndexManager[] indexManagersForQuery;
    if ( filterDefinitions != null && !filterDefinitions.isEmpty() ) {
      indexManagersForQuery = indexShardingStrategy.getIndexManagersForQuery(
          filterDefinitions.values().toArray( new FullTextFilterImplementor[filterDefinitions.size()] )
      );
    }
    else {
      //no filter get all shards
      indexManagersForQuery = indexShardingStrategy.getIndexManagersForQuery( EMPTY_FULL_TEXT_FILTER_IMPLEMENTOR );
    }

    for ( IndexManager indexManager : indexManagersForQuery ) {
      if ( !indexManagersTarget.contains( indexManager ) ) {
        indexManagersTarget.add( indexManager );
      }
    }
  }

  private void buildFilters() {
    ChainedFilter chainedFilter = new ChainedFilter();
    if ( !filterDefinitions.isEmpty() ) {
      for ( FullTextFilterImpl fullTextFilter : filterDefinitions.values() ) {
        Filter filter = buildLuceneFilter( fullTextFilter );
        if ( filter != null ) {
          chainedFilter.addFilter( filter );
        }
      }
    }

    if ( userFilter != null ) {
      chainedFilter.addFilter( userFilter );
    }

    if ( getFacetManager().getFacetFilter() != null ) {
      chainedFilter.addFilter( facetManager.getFacetFilter() );
    }

    if ( chainedFilter.isEmpty() ) {
      filter = null;
    }
    else {
      filter = chainedFilter;
    }
  }

  /**
   * Builds a Lucene filter using the given <code>FullTextFilter</code>.
   *
   * @param fullTextFilter the Hibernate specific <code>FullTextFilter</code> used to create the
   * Lucene <code>Filter</code>.
   *
   * @return the Lucene filter mapped to the filter definition
   */
  private Filter buildLuceneFilter(FullTextFilterImpl fullTextFilter) {

    /*
     * FilterKey implementations and Filter(Factory) do not have to be threadsafe wrt their parameter injection
     * as FilterCachingStrategy ensure a memory barrier between concurrent thread calls
     */
    FilterDef def = searchFactoryImplementor.getFilterDefinition( fullTextFilter.getName() );
    //def can never be null, ti's guarded by enableFullTextFilter(String)

    if ( isPreQueryFilterOnly( def ) ) {
      return null;
    }

    Object instance = createFilterInstance( fullTextFilter, def );
    FilterKey key = createFilterKey( def, instance );

    // try to get the filter out of the cache
    Filter filter = cacheInstance( def.getCacheMode() ) ?
        searchFactoryImplementor.getFilterCachingStrategy().getCachedFilter( key ) :
        null;

    if ( filter == null ) {
      filter = createFilter( def, instance );

      // add filter to cache if we have to
      if ( cacheInstance( def.getCacheMode() ) ) {
        searchFactoryImplementor.getFilterCachingStrategy().addCachedFilter( key, filter );
      }
    }
    return filter;
  }

  private boolean isPreQueryFilterOnly(FilterDef def) {
    return def.getImpl().equals( ShardSensitiveOnlyFilter.class );
  }

  private Filter createFilter(FilterDef def, Object instance) {
    Filter filter;
    if ( def.getFactoryMethod() != null ) {
      try {
        filter = (Filter) def.getFactoryMethod().invoke( instance );
      }
      catch ( IllegalAccessException e ) {
        throw new SearchException(
            "Unable to access @Factory method: "
                + def.getImpl().getName() + "." + def.getFactoryMethod().getName(), e
        );
      }
      catch ( InvocationTargetException e ) {
        throw new SearchException(
            "Unable to access @Factory method: "
                + def.getImpl().getName() + "." + def.getFactoryMethod().getName(), e
        );
      }
      catch ( ClassCastException e ) {
        throw new SearchException(
            "@Key method does not return a org.apache.lucene.search.Filter class: "
                + def.getImpl().getName() + "." + def.getFactoryMethod().getName(), e
        );
      }
    }
    else {
      try {
        filter = (Filter) instance;
      }
      catch ( ClassCastException e ) {
        throw new SearchException(
            "Filter implementation does not implement the Filter interface: "
                + def.getImpl().getName() + ". "
                + ( def.getFactoryMethod() != null ? def.getFactoryMethod().getName() : "" ), e
        );
      }
    }

    filter = addCachingWrapperFilter( filter, def );
    return filter;
  }

  /**
   * Decides whether to wrap the given filter around a <code>CachingWrapperFilter<code>.
   *
   * @param filter the filter which maybe gets wrapped.
   * @param def The filter definition used to decide whether wrapping should occur or not.
   *
   * @return The original filter or wrapped filter depending on the information extracted from
   *         <code>def</code>.
   */
  private Filter addCachingWrapperFilter(Filter filter, FilterDef def) {
    if ( cacheResults( def.getCacheMode() ) ) {
      int cachingWrapperFilterSize = searchFactoryImplementor.getFilterCacheBitResultsSize();
      filter = new CachingWrapperFilter( filter, cachingWrapperFilterSize );
    }

    return filter;
  }

  private FilterKey createFilterKey(FilterDef def, Object instance) {
    FilterKey key = null;
    if ( !cacheInstance( def.getCacheMode() ) ) {
      return key; // if the filter is not cached there is no key!
    }

    if ( def.getKeyMethod() == null ) {
      key = new FilterKey() {
        public int hashCode() {
          return getImpl().hashCode();
        }

        public boolean equals(Object obj) {
          if ( !( obj instanceof FilterKey ) ) {
            return false;
          }
          FilterKey that = (FilterKey) obj;
          return this.getImpl().equals( that.getImpl() );
        }
      };
    }
    else {
      try {
        key = (FilterKey) def.getKeyMethod().invoke( instance );
      }
      catch ( IllegalAccessException e ) {
        throw new SearchException(
            "Unable to access @Key method: "
                + def.getImpl().getName() + "." + def.getKeyMethod().getName()
        );
      }
      catch ( InvocationTargetException e ) {
        throw new SearchException(
            "Unable to access @Key method: "
                + def.getImpl().getName() + "." + def.getKeyMethod().getName()
        );
      }
      catch ( ClassCastException e ) {
        throw new SearchException(
            "@Key method does not return FilterKey: "
                + def.getImpl().getName() + "." + def.getKeyMethod().getName()
        );
      }
    }
    key.setImpl( def.getImpl() );

    //Make sure Filters are isolated by filter def name
    StandardFilterKey wrapperKey = new StandardFilterKey();
    wrapperKey.addParameter( def.getName() );
    wrapperKey.addParameter( key );
    return wrapperKey;
  }

  private Object createFilterInstance(FullTextFilterImpl fullTextFilter,
                    FilterDef def) {
    Object instance;
    try {
      instance = def.getImpl().newInstance();
    }
    catch ( InstantiationException e ) {
      throw new SearchException( "Unable to create @FullTextFilterDef: " + def.getImpl(), e );
    }
    catch ( IllegalAccessException e ) {
      throw new SearchException( "Unable to create @FullTextFilterDef: " + def.getImpl(), e );
    }
    for ( Map.Entry<String, Object> entry : fullTextFilter.getParameters().entrySet() ) {
      def.invoke( entry.getKey(), instance, entry.getValue() );
    }
    if ( cacheInstance( def.getCacheMode() ) && def.getKeyMethod() == null && fullTextFilter.getParameters()
        .size() > 0 ) {
      throw new SearchException( "Filter with parameters and no @Key method: " + fullTextFilter.getName() );
    }
    return instance;
  }

  private org.apache.lucene.search.Query filterQueryByClasses(org.apache.lucene.search.Query luceneQuery) {
    if ( !needClassFilterClause ) {
      return luceneQuery;
    }
    else {
      //A query filter is more practical than a manual class filtering post query (esp on scrollable resultsets)
      //it also probably minimise the memory footprint
      BooleanQuery classFilter = new BooleanQuery();
      //annihilate the scoring impact of DocumentBuilderIndexedEntity.CLASS_FIELDNAME
      classFilter.setBoost( 0 );
      for ( Class clazz : classesAndSubclasses ) {
        Term t = new Term( ProjectionConstants.OBJECT_CLASS, clazz.getName() );
        TermQuery termQuery = new TermQuery( t );
        classFilter.add( termQuery, BooleanClause.Occur.SHOULD );
      }
      BooleanQuery filteredQuery = new BooleanQuery();
      filteredQuery.add( luceneQuery, BooleanClause.Occur.MUST );
      filteredQuery.add( classFilter, BooleanClause.Occur.MUST );
      return filteredQuery;
    }
  }

  private int max(int first, int totalHits) {
    if ( maxResults == null ) {
      return totalHits - 1;
    }
    else {
      return maxResults + first < totalHits ?
          first + maxResults - 1 :
          totalHits - 1;
    }
  }

  public SearchFactoryImplementor getSearchFactoryImplementor() {
    return searchFactoryImplementor;
  }

  private boolean useFieldCacheOnTypes() {
    if ( classesAndSubclasses.size() <= 1 ) {
      // force it to false, as we won't need classes at all
      return false;
    }
    return useFieldCacheOnClassTypes;
  }

  /**
   * @return The FieldCacheCollectorFactory to use for this query, or null to not use FieldCaches
   */
  private FieldCacheCollectorFactory getAppropriateIdFieldCollectorFactory() {
    Map<Class<?>, EntityIndexBinder<?>> builders = searchFactoryImplementor.getIndexBindingForEntity();
    Set<FieldCacheCollectorFactory> allCollectors = new HashSet<FieldCacheCollectorFactory>();
    // we need all documentBuilder to agree on type, fieldName, and enabling the option:
    FieldCacheCollectorFactory anyImplementation = null;
    for ( Class<?> clazz : classesAndSubclasses ) {
      EntityIndexBinder<?> docBuilder = builders.get( clazz );
      FieldCacheCollectorFactory fieldCacheCollectionFactory = docBuilder.getIdFieldCacheCollectionFactory();
      if ( fieldCacheCollectionFactory == null ) {
        // some implementation disable it, so we won't use it
        return null;
      }
      anyImplementation = fieldCacheCollectionFactory;
      allCollectors.add( fieldCacheCollectionFactory );
    }
    if ( allCollectors.size() != 1 ) {
      // some implementations have different requirements
      return null;
    }
    else {
      // they are all the same, return any:
      return anyImplementation;
    }
  }
}
TOP

Related Classes of org.hibernate.search.query.engine.impl.HSQueryImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.