package org.infinispan.query.impl; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.lucene.search.Explanation; import org.apache.lucene.search.Filter; import org.apache.lucene.search.Query; import org.apache.lucene.search.Sort; import org.hibernate.search.exception.SearchException; import org.hibernate.search.filter.FullTextFilter; import org.hibernate.search.query.engine.spi.DocumentExtractor; import org.hibernate.search.query.engine.spi.EntityInfo; import org.hibernate.search.query.engine.spi.FacetManager; import org.hibernate.search.query.engine.spi.HSQuery; import org.hibernate.search.query.engine.spi.TimeoutExceptionFactory; import org.hibernate.search.spi.SearchIntegrator; import org.infinispan.AdvancedCache; import org.infinispan.query.CacheQuery; import org.infinispan.query.FetchOptions; import org.infinispan.query.FetchOptions.FetchMode; import org.infinispan.query.ResultIterator; import org.infinispan.query.backend.KeyTransformationHandler; /** * Implementation class of the CacheQuery interface. * <p/> * * @author Navin Surtani * @author Sanne Grinovero <sanne@hibernate.org> (C) 2011 Red Hat Inc. * @author Marko Luksa */ public class CacheQueryImpl<E> implements CacheQuery<E> { /** * Since CacheQuery extends {@link Iterable} it is possible to implicitly invoke * {@link #iterator()} in an "enhanced for loop". * When using the {@link FetchMode#LAZY} it is mandatory to close the {@link ResultIterator}, * but users of the enhanced loop have no chance to invoke the method. * Therefore, it's important that the default fetch options use EAGER iteration. */ private static final FetchOptions DEFAULT_FETCH_OPTIONS = new FetchOptions().fetchMode(FetchMode.EAGER); protected final AdvancedCache<?, ?> cache; protected final KeyTransformationHandler keyTransformationHandler; protected HSQuery hSearchQuery; private ProjectionConverter projectionConverter; /** * Create a CacheQueryImpl based on a Lucene query. */ public CacheQueryImpl(Query luceneQuery, SearchIntegrator searchFactory, AdvancedCache<?, ?> cache, KeyTransformationHandler keyTransformationHandler, TimeoutExceptionFactory timeoutExceptionFactory, Class<?>... classes) { this(timeoutExceptionFactory == null ? searchFactory.createHSQuery(luceneQuery, classes) : searchFactory.createHSQuery(luceneQuery, classes).timeoutExceptionFactory(timeoutExceptionFactory), cache, keyTransformationHandler); } /** * Create a CacheQueryImpl based on a HSQuery. */ public CacheQueryImpl(HSQuery hSearchQuery, AdvancedCache<?, ?> cache, KeyTransformationHandler keyTransformationHandler) { this.hSearchQuery = hSearchQuery; this.cache = cache; this.keyTransformationHandler = keyTransformationHandler; } /** * Takes in a lucene filter and sets it to the filter field in the class. * * @param filter - lucene filter */ @Override public CacheQuery<E> filter(Filter filter) { hSearchQuery.filter(filter); return this; } /** * @return The result size of the query. */ @Override public int getResultSize() { return hSearchQuery.queryResultSize(); } @Override public CacheQuery<E> sort(Sort sort) { hSearchQuery.sort(sort); return this; } /** * Enable a given filter by its name. * * @param name of filter. * @return a FullTextFilter object. */ @Override public FullTextFilter enableFullTextFilter(String name) { return hSearchQuery.enableFullTextFilter(name); } /** * Disable a given filter by its name. * * @param name of filter. */ @Override public CacheQuery<E> disableFullTextFilter(String name) { hSearchQuery.disableFullTextFilter(name); return this; } /** * Sets the the result of the given integer value to the first result. * * @param firstResult index to be set. * @throws IllegalArgumentException if the index given is less than zero. */ @Override public CacheQuery<E> firstResult(int firstResult) { hSearchQuery.firstResult(firstResult); return this; } @Override public CacheQuery<E> maxResults(int maxResults) { hSearchQuery.maxResults(maxResults); return this; } @Override public ResultIterator<E> iterator() throws SearchException { return iterator(DEFAULT_FETCH_OPTIONS); } @Override public ResultIterator<E> iterator(FetchOptions fetchOptions) throws SearchException { if (fetchOptions.getFetchMode() == FetchOptions.FetchMode.EAGER) { hSearchQuery.getTimeoutManager().start(); List<EntityInfo> entityInfos = hSearchQuery.queryEntityInfos(); return filterNulls(new EagerIterator<>(entityInfos, getResultLoader(), fetchOptions.getFetchSize())); } else if (fetchOptions.getFetchMode() == FetchOptions.FetchMode.LAZY) { DocumentExtractor extractor = hSearchQuery.queryDocumentExtractor(); //triggers actual Lucene search return filterNulls(new LazyIterator<>(extractor, getResultLoader(), fetchOptions.getFetchSize())); } else { throw new IllegalArgumentException("Unknown FetchMode " + fetchOptions.getFetchMode()); } } private ResultIterator<E> filterNulls(ResultIterator<E> iterator) { return new NullFilteringResultIterator<>(iterator); } @Override public List<E> list() throws SearchException { hSearchQuery.getTimeoutManager().start(); final List<EntityInfo> entityInfos = hSearchQuery.queryEntityInfos(); return (List<E>) getResultLoader().load(entityInfos); } private QueryResultLoader getResultLoader() { return isProjected() ? getProjectionLoader() : getEntityLoader(); } private boolean isProjected() { return hSearchQuery.getProjectedFields() != null; } private ProjectionLoader getProjectionLoader() { return new ProjectionLoader(projectionConverter, getEntityLoader()); } private EntityLoader getEntityLoader() { return new EntityLoader(cache, keyTransformationHandler); } @Override public FacetManager getFacetManager() { return hSearchQuery.getFacetManager(); } @Override public Explanation explain(int documentId) { return hSearchQuery.explain(documentId); } @Override public CacheQuery<Object[]> projection(String... fields) { this.projectionConverter = new ProjectionConverter(fields, cache, keyTransformationHandler); hSearchQuery.projection(projectionConverter.getHSearchProjection()); return (CacheQuery<Object[]>) this; } @Override public CacheQuery<E> timeout(long timeout, TimeUnit timeUnit) { hSearchQuery.getTimeoutManager().setTimeout(timeout, timeUnit); return this; } }