package fr.openwide.core.jpa.more.business.search.query;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.bindgen.binding.AbstractBinding;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import fr.openwide.core.jpa.more.business.sort.ISort;
import fr.openwide.core.jpa.more.business.sort.SortUtils;
import fr.openwide.core.jpa.search.bridge.GenericEntityIdFieldBridge;
import fr.openwide.core.jpa.search.bridge.NullEncodingGenericEntityIdFieldBridge;
import fr.openwide.core.spring.util.lucene.search.LuceneUtils;
public abstract class AbstractHibernateSearchSearchQuery<T, S extends ISort<SortField>> extends AbstractSearchQuery<T, S> /* NOT Serializable */ {
private final Class<? extends T> mainClass;
private final Class<? extends T>[] classes;
private BooleanJunction<?> junction;
private FullTextEntityManager fullTextEntityManager;
@Autowired
private IHibernateSearchLuceneQueryFactory factory;
private FullTextQuery fullTextQuery;
private SearchQueryDefaultResult defaultResult = SearchQueryDefaultResult.EVERYTHING;
@SuppressWarnings("unchecked")
@SafeVarargs
protected AbstractHibernateSearchSearchQuery(Class<? extends T> clazz, S ... defaultSorts) {
this(new Class[] { clazz }, defaultSorts);
}
@SafeVarargs
protected AbstractHibernateSearchSearchQuery(Class<? extends T>[] classes, S ... defaultSorts) {
super(defaultSorts);
this.mainClass = classes[0];
this.classes = Arrays.copyOf(classes, classes.length);
}
@PostConstruct
private void init() {
this.fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
this.factory.setDefaultClass(mainClass);
this.junction = getDefaultQueryBuilder().bool();
}
protected Analyzer getAnalyzer(Class<?> clazz) {
return this.factory.getAnalyzer(clazz);
}
/**
* @deprecated Use {@link #getDefaultAnalyzer()} instead.
*/
@Deprecated
protected Analyzer getAnalyzer() {
return getDefaultAnalyzer();
}
protected Analyzer getDefaultAnalyzer() {
return this.factory.getDefaultAnalyzer();
}
protected QueryBuilder getDefaultQueryBuilder() {
return this.factory.getDefaultQueryBuilder();
}
protected IHibernateSearchLuceneQueryFactory getFactory() {
return this.factory;
}
protected FullTextEntityManager getFullTextEntityManager() {
return fullTextEntityManager;
}
protected final SearchQueryDefaultResult getDefaultResult() {
return defaultResult;
}
protected final void setDefaultResult(SearchQueryDefaultResult defaultResult) {
this.defaultResult = defaultResult;
}
// Junction appender
// > Must
protected void must(Query query) {
if (query != null) {
junction.must(query);
}
}
protected void mustNot(Query query) {
if (query != null) {
junction.must(query).not();
}
}
protected void mustIfNotNull(BooleanJunction<?> junction, Query ... queries) {
for (Query query : queries) {
if (query != null) {
junction.must(query);
}
}
}
// > Should
protected void should(Query query) {
if (query != null) {
junction.should(query);
}
}
protected void shouldIfNotNull(BooleanJunction<?> junction, Query ... queries) {
for (Query query : queries) {
if (query != null) {
junction.should(query);
}
}
}
// List and count
/**
* Allow to add filter before generating the full text query.<br />
* Sample:
* <ul>
* <li><code>must(matchIfGiven(Bindings.company().manager().organization(), organization))</code></li>
* <li><code>must(matchIfGiven(Bindings.company().status(), CompanyStatus.ACTIVE))</code></li>
* </ul>
*/
protected void addFilterBeforeCreateQuery() {
// Nothing
}
private FullTextQuery getFullTextQuery() {
if (fullTextQuery == null) {
addFilterBeforeCreateQuery();
if (junction.isEmpty()) {
switch (getDefaultResult()) {
case EVERYTHING:
junction.must(getDefaultQueryBuilder().all().createQuery());
break;
case NOTHING:
junction.must(LuceneUtils.NO_RESULT_QUERY);
break;
}
}
fullTextQuery = fullTextEntityManager.createFullTextQuery(junction.createQuery(), classes);
}
return fullTextQuery;
}
private FullTextQuery getFullTextQueryList(Long offset, Long limit) {
FullTextQuery fullTextQuery = getFullTextQuery();
if (offset != null) {
fullTextQuery.setFirstResult(Ints.saturatedCast(offset));
}
if (limit != null) {
fullTextQuery.setMaxResults(Ints.saturatedCast(limit));
}
Sort sort = SortUtils.getLuceneSortWithDefaults(sortMap, defaultSorts);
if (sort != null && sort.getSort().length > 0) {
fullTextQuery.setSort(sort);
}
return fullTextQuery;
}
@Override
@Transactional(readOnly = true)
@SuppressWarnings("unchecked")
public final List<T> fullList() {
return getFullTextQueryList(null, null).getResultList();
}
@Override
@Transactional(readOnly = true)
@SuppressWarnings("unchecked")
public final List<T> list(long offset, long limit) {
return getFullTextQueryList(offset, limit).getResultList();
}
/**
* <b>Warning : </b>To be projected, the field should be stored in the document.<br>
* You can store the field in the document with the following method : {@link Field#store()}.
* @param offset
* @param limit
* @param field The field path
*/
protected <Q> List<Q> listProjection(Long offset, Long limit, String field) {
@SuppressWarnings("unchecked")
List<Object[]> projections = getFullTextQueryList(offset, limit).setProjection(field).getResultList();
return Lists.transform(projections, new Function<Object[], Q>() {
@SuppressWarnings("unchecked")
@Override
public Q apply(Object[] input) {
return (Q) input[0];
}
});
}
@Override
@Transactional(readOnly = true)
public long count() {
return getFullTextQuery().getResultSize();
}
// Query factory
// > Match null
/**
* <strong>Be careful</strong>: using this method needs null values to be indexed.
* You can use {@link NullEncodingGenericEntityIdFieldBridge} instead of the classical {@link GenericEntityIdFieldBridge} for example.
*/
protected Query matchNull(AbstractBinding<?, ?> binding) {
return getFactory().matchNull(binding);
}
protected Query matchNull(QueryBuilder builder, AbstractBinding<?, ?> binding) {
return getFactory().matchNull(builder, binding);
}
protected Query matchNull(String fieldPath) {
return getFactory().matchNull(fieldPath);
}
protected Query matchNull(QueryBuilder builder, String fieldPath) {
return getFactory().matchNull(builder, fieldPath);
}
// > Match if given
protected <P> Query matchIfGiven(AbstractBinding<?, P> binding, P value) {
return getFactory().matchIfGiven(binding, value);
}
protected <P> Query matchIfGiven(QueryBuilder builder, AbstractBinding<?, P> binding, P value) {
return getFactory().matchIfGiven(builder, binding, value);
}
protected <P> Query matchIfGiven(String fieldPath, P value) {
return getFactory().matchIfGiven(fieldPath, value);
}
protected <P> Query matchIfGiven(QueryBuilder builder, String fieldPath, P value) {
return getFactory().matchIfGiven(builder, fieldPath, value);
}
// > Match one term if given
protected Query matchOneTermIfGiven(AbstractBinding<?, String> binding, String terms) {
return getFactory().matchOneTermIfGiven(binding, terms);
}
protected Query matchOneTermIfGiven(QueryBuilder builder, AbstractBinding<?, String> binding, String terms) {
return getFactory().matchOneTermIfGiven(builder, binding, terms);
}
protected Query matchOneTermIfGiven(String fieldPath, String terms) {
return getFactory().matchOneTermIfGiven(fieldPath, terms);
}
protected Query matchOneTermIfGiven(QueryBuilder builder, String fieldPath, String terms) {
return getFactory().matchOneTermIfGiven(builder, fieldPath, terms);
}
// > Match all terms if given
@SafeVarargs
protected final Query matchAllTermsIfGiven(Analyzer analyzer, String terms, AbstractBinding<?, String> binding, AbstractBinding<?, String> ... otherBindings) {
return getFactory().matchAllTermsIfGiven(analyzer, terms, binding, otherBindings);
}
@SafeVarargs
protected final Query matchAllTermsIfGiven(String terms, AbstractBinding<?, String> binding, AbstractBinding<?, String> ... otherBindings) {
return getFactory().matchAllTermsIfGiven(terms, binding, otherBindings);
}
protected Query matchAllTermsIfGiven(String terms, Iterable<String> fieldPaths) {
return getFactory().matchAllTermsIfGiven(terms, fieldPaths);
}
protected Query matchAllTermsIfGiven(Analyzer analyzer, String terms, Iterable<String> fieldPaths) {
return getFactory().matchAllTermsIfGiven(analyzer, terms, fieldPaths);
}
// > Match autocomplete
@SafeVarargs
protected final Query matchAutocompleteIfGiven(Analyzer analyzer, String terms, AbstractBinding<?, String> binding, AbstractBinding<?, String> ... otherBindings) {
return getFactory().matchAutocompleteIfGiven(analyzer, terms, binding, otherBindings);
}
@SafeVarargs
protected final Query matchAutocompleteIfGiven(String terms, AbstractBinding<?, String> binding, AbstractBinding<?, String> ... otherBindings) {
return getFactory().matchAutocompleteIfGiven(terms, binding, otherBindings);
}
protected Query matchAutocompleteIfGiven(String terms, Iterable<String> fieldPaths) {
return getFactory().matchAutocompleteIfGiven(terms, fieldPaths);
}
protected Query matchAutocompleteIfGiven(Analyzer analyzer, String terms, Iterable<String> fieldPaths) {
return getFactory().matchAutocompleteIfGiven(analyzer, terms, fieldPaths);
}
// > Match fuzzy
@SafeVarargs
protected final Query matchFuzzyIfGiven(Analyzer analyzer, String terms, Integer maxEditDistance,
AbstractBinding<?, String> binding, AbstractBinding<?, String> ... otherBindings) {
return getFactory().matchFuzzyIfGiven(analyzer, terms, maxEditDistance, binding, otherBindings);
}
@SafeVarargs
protected final Query matchFuzzyIfGiven(String terms, Integer maxEditDistance,
AbstractBinding<?, String> binding, AbstractBinding<?, String> ... otherBindings) {
return getFactory().matchFuzzyIfGiven(terms, maxEditDistance, binding, otherBindings);
}
protected Query matchFuzzyIfGiven(String terms, Integer maxEditDistance, Iterable<String> fieldPaths) {
return getFactory().matchFuzzyIfGiven(terms, maxEditDistance, fieldPaths);
}
protected Query matchFuzzyIfGiven(Analyzer analyzer, String terms, Integer maxEditDistance, Iterable<String> fieldPaths) {
return getFactory().matchFuzzyIfGiven(analyzer, terms, maxEditDistance, fieldPaths);
}
// > Be included if given
protected <P> Query beIncludedIfGiven(AbstractBinding<?, ? extends Collection<P>> binding, P value) {
return getFactory().beIncludedIfGiven(binding, value);
}
protected <P> Query beIncludedIfGiven(QueryBuilder builder, AbstractBinding<?, ? extends Collection<P>> binding, P value) {
return getFactory().beIncludedIfGiven(builder, binding, value);
}
protected <P> Query beIncludedIfGiven(String fieldPath, P value) {
return getFactory().beIncludedIfGiven(fieldPath, value);
}
protected <P> Query beIncludedIfGiven(QueryBuilder builder, String fieldPath, P value) {
return getFactory().beIncludedIfGiven(builder, fieldPath, value);
}
// > Match one if given
protected <P> Query matchOneIfGiven(AbstractBinding<?, P> binding, Collection<? extends P> possibleValues) {
return getFactory().matchOneIfGiven(binding, possibleValues);
}
protected <P> Query matchOneIfGiven(QueryBuilder builder, AbstractBinding<?, P> binding, Collection<? extends P> possibleValues) {
return getFactory().matchOneIfGiven(builder, binding, possibleValues);
}
protected <P> Query matchOneIfGiven(String fieldPath, Collection<? extends P> possibleValues) {
return getFactory().matchOneIfGiven(fieldPath, possibleValues);
}
protected <P> Query matchOneIfGiven(QueryBuilder builder, String fieldPath, Collection<? extends P> possibleValues) {
return getFactory().matchOneIfGiven(builder, fieldPath, possibleValues);
}
// > Match all if given
protected <P> Query matchAllIfGiven(AbstractBinding<?, ? extends Collection<P>> binding, Collection<? extends P> possibleValues) {
return getFactory().matchAllIfGiven(binding, possibleValues);
}
protected <P> Query matchAllIfGiven(QueryBuilder builder, AbstractBinding<?, ? extends Collection<P>> binding,
Collection<? extends P> possibleValues) {
return getFactory().matchAllIfGiven(builder, binding, possibleValues);
}
protected <P> Query matchAllIfGiven(String fieldPath, Collection<? extends P> possibleValues) {
return getFactory().matchAllIfGiven(fieldPath, possibleValues);
}
protected <P> Query matchAllIfGiven(QueryBuilder builder, String fieldPath, Collection<? extends P> values) {
return getFactory().matchAllIfGiven(builder, fieldPath, values);
}
// > Match if true
protected Query matchIfTrue(AbstractBinding<?, Boolean> binding, boolean value, Boolean mustMatch) {
return getFactory().matchIfTrue(binding, value, mustMatch);
}
protected Query matchIfTrue(QueryBuilder builder, AbstractBinding<?, Boolean> binding, boolean value, Boolean mustMatch) {
return getFactory().matchIfTrue(builder, binding, value, mustMatch);
}
protected <P> Query matchIfTrue(String fieldPath, boolean value, Boolean mustMatch) {
return getFactory().matchIfTrue(fieldPath, value, mustMatch);
}
protected Query matchIfTrue(QueryBuilder builder, String fieldPath, boolean value, Boolean mustMatch) {
return getFactory().matchIfTrue(builder, fieldPath, value, mustMatch);
}
// > Match range (min, max, both)
protected <P> Query matchRangeMin(AbstractBinding<?, P> binding, P min) {
return getFactory().matchRangeMin(binding, min);
}
protected <P> Query matchRangeMin(QueryBuilder builder, AbstractBinding<?, P> binding, P min) {
return getFactory().matchRangeMin(builder, binding, min);
}
protected <P> Query matchRangeMin(String fieldPath, P min) {
return getFactory().matchRangeMin(fieldPath, min);
}
protected <P> Query matchRangeMin(QueryBuilder builder, String fieldPath, P min) {
return getFactory().matchRangeMin(builder, fieldPath, min);
}
protected <P> Query matchRangeMax(AbstractBinding<?, P> binding, P max) {
return getFactory().matchRangeMax(binding, max);
}
protected <P> Query matchRangeMax(QueryBuilder builder, AbstractBinding<?, P> binding, P max) {
return getFactory().matchRangeMax(builder, binding, max);
}
protected <P> Query matchRangeMax(String fieldPath, P max) {
return getFactory().matchRangeMax(fieldPath, max);
}
protected <P> Query matchRangeMax(QueryBuilder builder, String fieldPath, P max) {
return getFactory().matchRangeMax(builder, fieldPath, max);
}
protected <P> Query matchRange(AbstractBinding<?, P> binding, P min, P max) {
return getFactory().matchRange(binding, min, max);
}
protected <P> Query matchRange(QueryBuilder builder, AbstractBinding<?, P> binding, P min, P max) {
return getFactory().matchRange(builder, binding, min, max);
}
protected <P> Query matchRange(String fieldPath, P min, P max) {
return getFactory().matchRange(fieldPath, min, max);
}
protected <P> Query matchRange(QueryBuilder builder, String fieldPath, P min, P max) {
return getFactory().matchRange(builder, fieldPath, min, max);
}
}