package jpasearch.repository.util; import static com.google.common.base.Throwables.propagate; import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.util.List; import javax.inject.Named; import javax.inject.Singleton; import jpasearch.repository.query.selector.ObjectTermSelector; import jpasearch.repository.query.selector.StringTermSelector; import org.apache.lucene.search.Query; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.query.dsl.BooleanJunction; import org.hibernate.search.query.dsl.QueryBuilder; import org.hibernate.search.query.dsl.TermMatchingContext; import org.hibernate.search.query.dsl.WildcardContext; @Named @Singleton public class DefaultLuceneQueryBuilder implements LuceneQueryBuilder { private static final String PUNCTUATION = "\\p{Punct}|\\p{Space}"; @Override public <T> Query build(FullTextEntityManager fullTextEntityManager, StringTermSelector<T> termSelector, Class<? extends T> type) { QueryBuilder builder = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(type).get(); BooleanJunction<?> context = builder.bool(); boolean valid = false; if (termSelector.isNotEmpty()) { boolean hasTerms = false; BooleanJunction<?> termContext = builder.bool(); for (String selected : termSelector.getSelected()) { if (isNotBlank(selected)) { String[] values = selected.split(PUNCTUATION); for (String value : values) { if (isNotBlank(value) && (value.length() > 2)) { List<String> fields = termSelector.getPaths(); BooleanJunction<?> valueContext = builder.bool(); addFuzzyMatch(builder, value, fields, termSelector.getSearchSimilarity(), valueContext); addKeywordMatch(builder, value, fields, valueContext); // wildcard search // no #onFields on wildcardContext WildcardContext wildcardContext = builder.keyword().wildcard(); TermMatchingContext termMatchingContext = null; for (String field : fields) { if (termMatchingContext != null) { termMatchingContext = termMatchingContext.andField(field); } else { termMatchingContext = wildcardContext.onField(field); } } valueContext.should(termMatchingContext. // matching("*" + value + "*").createQuery()); if (termSelector.isOrMode()) { termContext.should(valueContext.createQuery()); } else { termContext.must(valueContext.createQuery()); } hasTerms = true; } } } } if (hasTerms) { context.must(termContext.createQuery()); valid = true; } } return createQuery(builder, context, valid); } @Override public <T> Query build(FullTextEntityManager fullTextEntityManager, ObjectTermSelector<T> termSelector, Class<? extends T> type) { QueryBuilder builder = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(type).get(); BooleanJunction<?> context = builder.bool(); boolean valid = false; if (termSelector.isNotEmpty()) { boolean hasTerms = false; BooleanJunction<?> termContext = builder.bool(); for (Object selected : termSelector.getSelected()) { if ((selected != null) && isNotBlank(selected.toString())) { List<String> fields = termSelector.getPaths(); BooleanJunction<?> valueContext = builder.bool(); addFuzzyMatch(builder, selected, fields, termSelector.getSearchSimilarity(), valueContext); addKeywordMatch(builder, selected, fields, valueContext); if (termSelector.isOrMode()) { termContext.should(valueContext.createQuery()); } else { termContext.must(valueContext.createQuery()); } hasTerms = true; } } if (hasTerms) { context.must(termContext.createQuery()); valid = true; } } return createQuery(builder, context, valid); } private <T> void addFuzzyMatch(QueryBuilder builder, Object value, List<String> fields, Integer editDistance, BooleanJunction<?> valueContext) { if (editDistance != null) { valueContext.should(builder.keyword().fuzzy() // .withEditDistanceUpTo(editDistance) // .onFields(fields.toArray(new String[fields.size()])) // .matching(value).createQuery()); } } private void addKeywordMatch(QueryBuilder builder, Object selected, List<String> fields, BooleanJunction<?> valueContext) { valueContext.should(builder.keyword() // .onFields(fields.toArray(new String[fields.size()])) // .matching(selected).createQuery()); } private Query createQuery(QueryBuilder builder, BooleanJunction<?> context, boolean valid) { try { if (valid) { return context.createQuery(); } else { return builder.all().except(builder.all().createQuery()).createQuery(); } } catch (Exception e) { throw propagate(e); } } }