package ru.hflabs.rcd.lucene.criteria; import org.apache.lucene.search.*; import org.springframework.core.convert.converter.Converter; import org.springframework.util.StringUtils; import ru.hflabs.rcd.index.IndexedField; import ru.hflabs.rcd.model.Identifying; import ru.hflabs.rcd.model.criteria.FilterCriteriaValue; import ru.hflabs.rcd.storage.CriteriaBuilderTemplate; import ru.hflabs.rcd.term.Condition; import ru.hflabs.util.core.Pair; import ru.hflabs.util.lucene.LuceneBinderTransformer; import ru.hflabs.util.lucene.LuceneQueryUtil; import ru.hflabs.util.lucene.LuceneUtil; import javax.swing.*; import java.util.Date; import java.util.Set; import static ru.hflabs.util.lucene.LuceneQueryUtil.createTermQuery; import static ru.hflabs.util.lucene.LuceneUtil.valueToTerm; /** * Класс <class>LuceneCriteriaBuilder</class> реализует сервис построения поискового запроса * * @author Nazin Alexander */ public class LuceneCriteriaBuilder<E extends Identifying> extends CriteriaBuilderTemplate<E, LuceneCriteriaHolder, Query> { static { BooleanQuery.setMaxClauseCount(Integer.MAX_VALUE); } /** Сервис получаения индексированных полей */ private Converter<Pair<Class<E>, String>, IndexedField> class2fieldConverter; public void setClass2fieldConverter(Converter<Pair<Class<E>, String>, IndexedField> class2fieldConverter) { this.class2fieldConverter = class2fieldConverter; } /** * Возвращает {@link org.apache.lucene.search.BooleanClause.Occur} по {@link Condition} * * @param condition текущая операция * @return Возвращает {@link org.apache.lucene.search.BooleanClause.Occur} */ public static BooleanClause.Occur retrieveOccurByLogicalOperation(Condition condition) { switch (condition) { case OR: { return BooleanClause.Occur.SHOULD; } case AND: { return BooleanClause.Occur.MUST; } case NOT: { return BooleanClause.Occur.MUST_NOT; } default: { throw new UnsupportedOperationException( String.format("Condition '%s' not supported by '%s'", condition.name(), LuceneCriteriaBuilder.class.getSimpleName()) ); } } } /** * Создает запрос для пустого значения * * @param criteriaClass целевой класс критерии * @param criteriaField целевое поле * @return Возвращает запрос поиска */ private Query createQueryByEmptyValue(Class<E> criteriaClass, String criteriaField) { IndexedField indexedField = class2fieldConverter.convert(Pair.valueOf(criteriaClass, criteriaField)); if (Date.class.isAssignableFrom(indexedField.getType())) { return createTermQuery(indexedField.getName(), LuceneUtil.DATE_MIN_NULL_VALUE); } else { final BooleanQuery query = new BooleanQuery(); query.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST); query.add(new TermRangeQuery(criteriaField, null, null, true, true), BooleanClause.Occur.MUST_NOT); return query; } } /** * Создает запрос для НЕ пустого значения * * @param criteriaClass целевой класс критерии * @param criteriaField целевое поле * @return Возвращает запрос поиска */ private Query createQueryByNotEmptyValue(Class<E> criteriaClass, String criteriaField) { IndexedField indexedField = class2fieldConverter.convert(Pair.valueOf(criteriaClass, criteriaField)); if (Date.class.isAssignableFrom(indexedField.getType())) { final BooleanQuery query = new BooleanQuery(); query.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST); query.add(createTermQuery(indexedField.getName(), LuceneUtil.DATE_MIN_NULL_VALUE), BooleanClause.Occur.MUST_NOT); return query; } else { return new TermRangeQuery(indexedField.getName(), null, null, true, true); } } @Override public LuceneCriteriaHolder createEmptyCriteria(Class<E> criteriaClass) { return new LuceneCriteriaHolder(); } @Override protected LuceneCriteriaHolder appendFilterByEmptyValue(LuceneCriteriaHolder current, Class<E> criteriaClass, String criteriaField, FilterCriteriaValue.EmptyValue value) { return current.appendPredicate(createQueryByEmptyValue(criteriaClass, criteriaField), value.getCondition()); } @Override protected LuceneCriteriaHolder appendFilterByNotEmptyValue(LuceneCriteriaHolder current, Class<E> criteriaClass, String criteriaField, FilterCriteriaValue.NotEmptyValue value) { return current.appendPredicate(createQueryByNotEmptyValue(criteriaClass, criteriaField), value.getCondition()); } @Override protected LuceneCriteriaHolder appendFilterByBooleanValue(LuceneCriteriaHolder current, Class<E> criteriaClass, String criteriaField, FilterCriteriaValue.BooleanValue value) { return current.appendPredicate(createTermQuery(criteriaField, value.getValue()), value.getCondition()); } @Override protected LuceneCriteriaHolder appendFilterByEnumValues(LuceneCriteriaHolder current, Class<E> criteriaClass, String criteriaField, FilterCriteriaValue.EnumValues<?> value) { BooleanQuery query = new BooleanQuery(); BooleanClause.Occur occur = retrieveOccurByLogicalOperation(value.getClauseCondition()); for (Enum<?> enumValue : value.getValue()) { query.add(createTermQuery(criteriaField, enumValue), occur); } return current.appendPredicate(query, value.getCondition()); } @Override protected LuceneCriteriaHolder appendFilterByStringValue(LuceneCriteriaHolder current, Class<E> criteriaClass, String criteriaField, FilterCriteriaValue.StringValue value) { if (StringUtils.hasText(value.getValue())) { return current.appendPredicate(createTermQuery(criteriaField, value.getValue()), value.getCondition()); } return appendFilterByEmptyValue(current, criteriaClass, criteriaField, FilterCriteriaValue.EMPTY_VALUE); } @Override protected LuceneCriteriaHolder appendFilterByDateValue(LuceneCriteriaHolder current, Class<E> criteriaClass, String criteriaField, FilterCriteriaValue.DateValue value) { return current.appendPredicate( NumericRangeQuery.newLongRange( criteriaField, value.getStart() != null ? LuceneUtil.dateToLong(value.getStart()) : null, value.getEnd() != null ? LuceneUtil.dateToLong(value.getEnd()) : null, true, true ), value.getCondition() ); } @Override protected LuceneCriteriaHolder appendFilterByStringsValue(LuceneCriteriaHolder current, Class<E> criteriaClass, String criteriaField, FilterCriteriaValue.StringsValue value) { final BooleanQuery query = new BooleanQuery(); final BooleanClause.Occur occur = retrieveOccurByLogicalOperation(value.getClauseCondition()); for (String str : value.getValue()) { query.add( StringUtils.hasText(str) ? createTermQuery(criteriaField, str) : createQueryByEmptyValue(criteriaClass, criteriaField), occur ); } Query rewritedQuery = LuceneQueryUtil.rewriteBooleanQuery(query); if (rewritedQuery != null) { return current.appendPredicate(rewritedQuery, value.getCondition()); } return current; } @Override protected LuceneCriteriaHolder appendSearch(LuceneCriteriaHolder current, Class<E> criteriaClass, Set<String> values, Condition condition) { final BooleanQuery query = new BooleanQuery(); for (String word : values) { if (StringUtils.hasText(word)) { query.add(new PrefixQuery(valueToTerm(LuceneBinderTransformer.DEFAULT_SEARCH_FIELD, word)), BooleanClause.Occur.MUST); } } Query rewritedQuery = LuceneQueryUtil.rewriteBooleanQuery(query); if (rewritedQuery != null) { current.appendPredicate(rewritedQuery, condition); } return current; } @Override protected LuceneCriteriaHolder appendOrder(LuceneCriteriaHolder current, Class<E> criteriaClass, String orderKey, SortOrder orderValue) { IndexedField indexedField = class2fieldConverter.convert(Pair.valueOf(criteriaClass, orderKey)); SortField.Type sortFieldType = LuceneUtil.sortFieldTypeByClass(indexedField.getType()); return current.appendOrder(new SortField(orderKey, sortFieldType, SortOrder.DESCENDING.equals(orderValue))); } @Override protected LuceneCriteriaHolder appendDefaultOrder(LuceneCriteriaHolder current, Class<E> criteriaClass) { return appendOrder(current, criteriaClass, E.PRIMARY_KEY, SortOrder.ASCENDING); } }