View Javadoc
1   /*
2    * Copyright 2012-2017 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.helper;
17  
18  import static org.codelibs.core.stream.StreamUtil.stream;
19  
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Locale;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.UUID;
28  import java.util.function.Consumer;
29  import java.util.stream.Stream;
30  
31  import javax.annotation.PostConstruct;
32  import javax.annotation.Resource;
33  
34  import org.apache.lucene.index.Term;
35  import org.apache.lucene.queryparser.classic.ParseException;
36  import org.apache.lucene.queryparser.classic.QueryParser;
37  import org.apache.lucene.search.BooleanClause;
38  import org.apache.lucene.search.BooleanQuery;
39  import org.apache.lucene.search.BoostQuery;
40  import org.apache.lucene.search.FuzzyQuery;
41  import org.apache.lucene.search.MatchAllDocsQuery;
42  import org.apache.lucene.search.PhraseQuery;
43  import org.apache.lucene.search.PrefixQuery;
44  import org.apache.lucene.search.Query;
45  import org.apache.lucene.search.TermQuery;
46  import org.apache.lucene.search.TermRangeQuery;
47  import org.apache.lucene.search.WildcardQuery;
48  import org.apache.lucene.util.BytesRef;
49  import org.codelibs.core.lang.StringUtil;
50  import org.codelibs.fess.Constants;
51  import org.codelibs.fess.entity.FacetInfo;
52  import org.codelibs.fess.entity.GeoInfo;
53  import org.codelibs.fess.entity.QueryContext;
54  import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
55  import org.codelibs.fess.exception.InvalidQueryException;
56  import org.codelibs.fess.mylasta.direction.FessConfig;
57  import org.codelibs.fess.util.ComponentUtil;
58  import org.dbflute.optional.OptionalThing;
59  import org.elasticsearch.common.unit.Fuzziness;
60  import org.elasticsearch.index.query.BoolQueryBuilder;
61  import org.elasticsearch.index.query.QueryBuilder;
62  import org.elasticsearch.index.query.QueryBuilders;
63  import org.elasticsearch.index.query.RangeQueryBuilder;
64  import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder;
65  import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
66  import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
67  import org.elasticsearch.search.sort.SortBuilder;
68  import org.elasticsearch.search.sort.SortBuilders;
69  import org.elasticsearch.search.sort.SortOrder;
70  import org.lastaflute.core.message.UserMessages;
71  import org.lastaflute.web.util.LaRequestUtil;
72  
73  public class QueryHelper {
74  
75      private static final String ES_SCORE_FIELD = "_score";
76  
77      protected static final String SCORE_SORT_VALUE = "score";
78  
79      protected static final String SCORE_FIELD = "score";
80  
81      protected static final String INURL_FIELD = "inurl";
82  
83      @Resource
84      protected FessConfig fessConfig;
85  
86      @Resource
87      protected RoleQueryHelper roleQueryHelper;
88  
89      @Resource
90      protected SystemHelper systemHelper;
91  
92      @Resource
93      protected KeyMatchHelper keyMatchHelper;
94  
95      protected Set<String> apiResponseFieldSet;
96  
97      protected Set<String> highlightFieldSet = new HashSet<>();
98  
99      protected Set<String> notAnalyzedFieldSet;
100 
101     protected String[] responseFields;
102 
103     protected String[] cacheResponseFields;
104 
105     protected String[] highlightedFields;
106 
107     protected String[] searchFields;
108 
109     protected String[] facetFields;
110 
111     protected String sortPrefix = "sort:";
112 
113     protected String[] sortFields;
114 
115     protected String additionalQuery;
116 
117     protected boolean lowercaseWildcard = true;
118 
119     protected SortBuilder<?>[] defaultSortBuilders;
120 
121     protected String highlightPrefix = "hl_";
122 
123     protected FacetInfo defaultFacetInfo;
124 
125     protected GeoInfo defaultGeoInfo;
126 
127     protected Map<String, String> fieldBoostMap = new HashMap<>();
128 
129     protected List<FilterFunctionBuilder> boostFunctionList = new ArrayList<>();
130 
131     @PostConstruct
132     public void init() {
133         if (responseFields == null) {
134             responseFields = fessConfig.getQueryAdditionalResponseFields(//
135                     SCORE_FIELD, //
136                     fessConfig.getIndexFieldId(), //
137                     fessConfig.getIndexFieldDocId(), //
138                     fessConfig.getIndexFieldBoost(), //
139                     fessConfig.getIndexFieldContentLength(), //
140                     fessConfig.getIndexFieldHost(), //
141                     fessConfig.getIndexFieldSite(), //
142                     fessConfig.getIndexFieldLastModified(), //
143                     fessConfig.getIndexFieldTimestamp(), //
144                     fessConfig.getIndexFieldMimetype(), //
145                     fessConfig.getIndexFieldFiletype(), //
146                     fessConfig.getIndexFieldFilename(), //
147                     fessConfig.getIndexFieldCreated(), //
148                     fessConfig.getIndexFieldTitle(), //
149                     fessConfig.getIndexFieldDigest(), //
150                     fessConfig.getIndexFieldUrl(), //
151                     fessConfig.getIndexFieldThumbnail(), //
152                     fessConfig.getIndexFieldClickCount(), //
153                     fessConfig.getIndexFieldFavoriteCount(), //
154                     fessConfig.getIndexFieldConfigId(), //
155                     fessConfig.getIndexFieldLang(), //
156                     fessConfig.getIndexFieldHasCache());
157         }
158         if (cacheResponseFields == null) {
159             cacheResponseFields = fessConfig.getQueryAdditionalCacheResponseFields(//
160                     SCORE_FIELD, //
161                     fessConfig.getIndexFieldId(), //
162                     fessConfig.getIndexFieldDocId(), //
163                     fessConfig.getIndexFieldBoost(), //
164                     fessConfig.getIndexFieldContentLength(), //
165                     fessConfig.getIndexFieldHost(), //
166                     fessConfig.getIndexFieldSite(), //
167                     fessConfig.getIndexFieldLastModified(), //
168                     fessConfig.getIndexFieldTimestamp(), //
169                     fessConfig.getIndexFieldMimetype(), //
170                     fessConfig.getIndexFieldFiletype(), //
171                     fessConfig.getIndexFieldFilename(), //
172                     fessConfig.getIndexFieldCreated(), //
173                     fessConfig.getIndexFieldTitle(), //
174                     fessConfig.getIndexFieldDigest(), //
175                     fessConfig.getIndexFieldUrl(), //
176                     fessConfig.getIndexFieldClickCount(), //
177                     fessConfig.getIndexFieldFavoriteCount(), //
178                     fessConfig.getIndexFieldConfigId(), //
179                     fessConfig.getIndexFieldLang(), //
180                     fessConfig.getIndexFieldCache());
181         }
182         if (highlightedFields == null) {
183             highlightedFields = fessConfig.getQueryAdditionalHighlightedFields(fessConfig.getIndexFieldContent());
184         }
185         if (searchFields == null) {
186             searchFields = fessConfig.getQueryAdditionalSearchFields(//
187                     INURL_FIELD, //
188                     fessConfig.getIndexFieldUrl(), //
189                     fessConfig.getIndexFieldDocId(), //
190                     fessConfig.getIndexFieldHost(), //
191                     fessConfig.getIndexFieldTitle(), //
192                     fessConfig.getIndexFieldContent(), //
193                     fessConfig.getIndexFieldContentLength(), //
194                     fessConfig.getIndexFieldLastModified(), //
195                     fessConfig.getIndexFieldTimestamp(), //
196                     fessConfig.getIndexFieldMimetype(), //
197                     fessConfig.getIndexFieldFiletype(), //
198                     fessConfig.getIndexFieldFilename(), //
199                     fessConfig.getIndexFieldLabel(), //
200                     fessConfig.getIndexFieldSegment(), //
201                     fessConfig.getIndexFieldAnchor(), //
202                     fessConfig.getIndexFieldClickCount(), //
203                     fessConfig.getIndexFieldFavoriteCount(), //
204                     fessConfig.getIndexFieldLang());
205         }
206         if (facetFields == null) {
207             facetFields = fessConfig.getQueryAdditionalFacetFields(//
208                     fessConfig.getIndexFieldUrl(), //
209                     fessConfig.getIndexFieldHost(), //
210                     fessConfig.getIndexFieldTitle(), //
211                     fessConfig.getIndexFieldContent(), //
212                     fessConfig.getIndexFieldContentLength(), //
213                     fessConfig.getIndexFieldLastModified(), //
214                     fessConfig.getIndexFieldTimestamp(), //
215                     fessConfig.getIndexFieldMimetype(), //
216                     fessConfig.getIndexFieldFiletype(), //
217                     fessConfig.getIndexFieldLabel(), //
218                     fessConfig.getIndexFieldSegment());
219         }
220         if (sortFields == null) {
221             sortFields = fessConfig.getQueryAdditionalSortFields(//
222                     SCORE_SORT_VALUE, //
223                     fessConfig.getIndexFieldFilename(), //
224                     fessConfig.getIndexFieldCreated(), //
225                     fessConfig.getIndexFieldContentLength(), //
226                     fessConfig.getIndexFieldLastModified(), //
227                     fessConfig.getIndexFieldTimestamp(), //
228                     fessConfig.getIndexFieldClickCount(), //
229                     fessConfig.getIndexFieldFavoriteCount());
230         }
231         if (apiResponseFieldSet == null) {
232             setApiResponseFields(fessConfig.getQueryAdditionalApiResponseFields(//
233                     fessConfig.getResponseFieldContentDescription(), //
234                     fessConfig.getResponseFieldContentTitle(), //
235                     fessConfig.getResponseFieldSitePath(), //
236                     fessConfig.getResponseFieldUrlLink(), //
237                     fessConfig.getIndexFieldId(), //
238                     fessConfig.getIndexFieldDocId(), //
239                     fessConfig.getIndexFieldBoost(), //
240                     fessConfig.getIndexFieldContentLength(), //
241                     fessConfig.getIndexFieldHost(), //
242                     fessConfig.getIndexFieldSite(), //
243                     fessConfig.getIndexFieldLastModified(), //
244                     fessConfig.getIndexFieldTimestamp(), //
245                     fessConfig.getIndexFieldMimetype(), //
246                     fessConfig.getIndexFieldFiletype(), //
247                     fessConfig.getIndexFieldFilename(), //
248                     fessConfig.getIndexFieldCreated(), //
249                     fessConfig.getIndexFieldTitle(), //
250                     fessConfig.getIndexFieldDigest(), //
251                     fessConfig.getIndexFieldUrl()));
252         }
253         if (notAnalyzedFieldSet == null) {
254             setNotAnalyzedFields(fessConfig.getQueryAdditionalNotAnalyzedFields(//
255                     fessConfig.getIndexFieldAnchor(), //
256                     fessConfig.getIndexFieldBoost(), //
257                     fessConfig.getIndexFieldClickCount(), //
258                     fessConfig.getIndexFieldConfigId(), //
259                     fessConfig.getIndexFieldContentLength(), //
260                     fessConfig.getIndexFieldCreated(), //
261                     fessConfig.getIndexFieldDocId(), //
262                     fessConfig.getIndexFieldExpires(), //
263                     fessConfig.getIndexFieldFavoriteCount(), //
264                     fessConfig.getIndexFieldFiletype(), //
265                     fessConfig.getIndexFieldFilename(), //
266                     fessConfig.getIndexFieldHasCache(), //
267                     fessConfig.getIndexFieldHost(), //
268                     fessConfig.getIndexFieldId(), //
269                     fessConfig.getIndexFieldLabel(), //
270                     fessConfig.getIndexFieldLang(), //
271                     fessConfig.getIndexFieldLastModified(), //
272                     fessConfig.getIndexFieldMimetype(), //
273                     fessConfig.getIndexFieldParentId(), //
274                     fessConfig.getIndexFieldRole(), //
275                     fessConfig.getIndexFieldSegment(), //
276                     fessConfig.getIndexFieldSite(), //
277                     fessConfig.getIndexFieldTimestamp(), //
278                     fessConfig.getIndexFieldUrl(), //
279                     fessConfig.getIndexFieldVersion()));
280         }
281     }
282 
283     public QueryContext build(final SearchRequestType searchRequestType, final String query, final Consumer<QueryContext> context) {
284         String q;
285         if (additionalQuery != null && StringUtil.isNotBlank(query)) {
286             q = query + " " + additionalQuery;
287         } else {
288             q = query;
289         }
290 
291         final QueryContext queryContext = new QueryContext(q, true);
292         buildBaseQuery(queryContext, context);
293         buildBoostQuery(queryContext);
294         buildRoleQuery(queryContext, searchRequestType);
295         buildVirtualHostQuery(queryContext, searchRequestType);
296 
297         if (!queryContext.hasSorts() && defaultSortBuilders != null) {
298             queryContext.addSorts(defaultSortBuilders);
299         }
300         return queryContext;
301     }
302 
303     protected void buildVirtualHostQuery(final QueryContext queryContext, final SearchRequestType searchRequestType) {
304         switch (searchRequestType) {
305         case ADMIN_SEARCH:
306             // nothing to do
307             break;
308         default:
309             final String key = fessConfig.getVirtualHostKey();
310             if (StringUtil.isNotBlank(key)) {
311                 queryContext.addQuery(boolQuery -> {
312                     boolQuery.filter(QueryBuilders.termQuery(fessConfig.getIndexFieldVirtualHost(), key));
313                 });
314             }
315             break;
316         }
317     }
318 
319     protected void buildRoleQuery(final QueryContext queryContext, final SearchRequestType searchRequestType) {
320         if (roleQueryHelper != null && queryContext.roleQueryEnabled()) {
321             final Set<String> roleSet = roleQueryHelper.build(searchRequestType);
322             if (!roleSet.isEmpty()) {
323                 queryContext.addQuery(boolQuery -> {
324                     final BoolQueryBuilder roleQuery = QueryBuilders.boolQuery();
325                     roleSet.stream().forEach(name -> {
326                         roleQuery.should(QueryBuilders.termQuery(fessConfig.getIndexFieldRole(), name));
327                     });
328                     boolQuery.filter(roleQuery);
329                 });
330             }
331         }
332     }
333 
334     protected void buildBoostQuery(final QueryContext queryContext) {
335         queryContext.addFunctionScore(list -> {
336             list.add(new FilterFunctionBuilder(ScoreFunctionBuilders.fieldValueFactorFunction(fessConfig.getIndexFieldBoost())));
337             if (keyMatchHelper != null) {
338                 keyMatchHelper.buildQuery(queryContext.getDefaultKeyword(), list);
339             }
340             list.addAll(boostFunctionList);
341         });
342     }
343 
344     public void buildBaseQuery(final QueryContext queryContext, final Consumer<QueryContext> context) {
345         try {
346             final Query query = getQueryParser().parse(queryContext.getQueryString());
347             final QueryBuilder queryBuilder = convertQuery(queryContext, query, 1.0f);
348             if (queryBuilder != null) {
349                 queryContext.setQueryBuilder(queryBuilder);
350             } else {
351                 queryContext.setQueryBuilder(QueryBuilders.matchAllQuery());
352             }
353             // TODO options query
354             context.accept(queryContext);
355         } catch (final ParseException e) {
356             throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryParseError(UserMessages.GLOBAL_PROPERTY_KEY),
357                     "Invalid query: " + queryContext.getQueryString(), e);
358         }
359     }
360 
361     protected QueryParser getQueryParser() {
362         return ComponentUtil.getQueryParser();
363     }
364 
365     protected QueryBuilder convertQuery(final QueryContext context, final Query query, final float boost) {
366         if (query instanceof TermQuery) {
367             return convertTermQuery(context, (TermQuery) query, boost);
368         } else if (query instanceof TermRangeQuery) {
369             return convertTermRangeQuery(context, (TermRangeQuery) query, boost);
370         } else if (query instanceof PhraseQuery) {
371             return convertPhraseQuery(context, (PhraseQuery) query, boost);
372         } else if (query instanceof FuzzyQuery) {
373             return convertFuzzyQuery(context, (FuzzyQuery) query, boost);
374         } else if (query instanceof PrefixQuery) {
375             return convertPrefixQuery(context, (PrefixQuery) query, boost);
376         } else if (query instanceof WildcardQuery) {
377             return convertWildcardQuery(context, (WildcardQuery) query, boost);
378         } else if (query instanceof BooleanQuery) {
379             final BooleanQuery booleanQuery = (BooleanQuery) query;
380             return convertBooleanQuery(context, booleanQuery, boost);
381         } else if (query instanceof MatchAllDocsQuery) {
382             return QueryBuilders.matchAllQuery();
383         } else if (query instanceof BoostQuery) {
384             final BoostQuery boostQuery = (BoostQuery) query;
385             return convertQuery(context, boostQuery.getQuery(), boostQuery.getBoost());
386         }
387         throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY), "Unknown q: "
388                 + query.getClass() + " => " + query);
389     }
390 
391     protected QueryBuilder convertBooleanQuery(final QueryContext context, final BooleanQuery booleanQuery, final float boost) {
392         final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
393         for (final BooleanClause clause : booleanQuery.clauses()) {
394             final QueryBuilder queryBuilder = convertQuery(context, clause.getQuery(), boost);
395             if (queryBuilder != null) {
396                 switch (clause.getOccur()) {
397                 case MUST:
398                     boolQuery.must(queryBuilder);
399                     break;
400                 case SHOULD:
401                     boolQuery.should(queryBuilder);
402                     break;
403                 case MUST_NOT:
404                     boolQuery.mustNot(queryBuilder);
405                     break;
406                 default:
407                     break;
408                 }
409             }
410         }
411         return boolQuery;
412     }
413 
414     protected String toLowercaseWildcard(final String value) {
415         if (lowercaseWildcard) {
416             return value.toLowerCase(Locale.ROOT);
417         }
418         return value;
419     }
420 
421     protected QueryBuilder convertWildcardQuery(final QueryContext context, final WildcardQuery wildcardQuery, final float boost) {
422         final String field = wildcardQuery.getField();
423         if (Constants.DEFAULT_FIELD.equals(field)) {
424             context.addFieldLog(field, wildcardQuery.getTerm().text());
425             return buildDefaultQueryBuilder((f, b) -> QueryBuilders.wildcardQuery(f, toLowercaseWildcard(wildcardQuery.getTerm().text()))
426                     .boost(b * boost));
427         } else if (isSearchField(field)) {
428             context.addFieldLog(field, wildcardQuery.getTerm().text());
429             return QueryBuilders.wildcardQuery(field, toLowercaseWildcard(wildcardQuery.getTerm().text())).boost(boost);
430         } else {
431             final String query = wildcardQuery.getTerm().toString();
432             final String origQuery = toLowercaseWildcard(query);
433             context.addFieldLog(Constants.DEFAULT_FIELD, query);
434             context.addHighlightedQuery(origQuery);
435             return buildDefaultQueryBuilder((f, b) -> QueryBuilders.wildcardQuery(f, origQuery).boost(b * boost));
436         }
437     }
438 
439     protected QueryBuilder convertPrefixQuery(final QueryContext context, final PrefixQuery prefixQuery, final float boost) {
440         final String field = prefixQuery.getField();
441         if (Constants.DEFAULT_FIELD.equals(field)) {
442             context.addFieldLog(field, prefixQuery.getPrefix().text());
443             return buildDefaultQueryBuilder((f, b) -> QueryBuilders.prefixQuery(f, toLowercaseWildcard(prefixQuery.getPrefix().text()))
444                     .boost(b * boost));
445         } else if (isSearchField(field)) {
446             context.addFieldLog(field, prefixQuery.getPrefix().text());
447             return QueryBuilders.prefixQuery(field, toLowercaseWildcard(prefixQuery.getPrefix().text())).boost(boost);
448         } else {
449             final String query = prefixQuery.getPrefix().toString();
450             final String origQuery = toLowercaseWildcard(query);
451             context.addFieldLog(Constants.DEFAULT_FIELD, query);
452             context.addHighlightedQuery(origQuery);
453             return buildDefaultQueryBuilder((f, b) -> QueryBuilders.prefixQuery(f, origQuery).boost(b * boost));
454         }
455     }
456 
457     protected QueryBuilder convertFuzzyQuery(final QueryContext context, final FuzzyQuery fuzzyQuery, final float boost) {
458         final Term term = fuzzyQuery.getTerm();
459         final String field = term.field();
460         // TODO fuzzy value
461         if (Constants.DEFAULT_FIELD.equals(field)) {
462             context.addFieldLog(field, term.text());
463             return buildDefaultQueryBuilder((f, b) -> QueryBuilders.fuzzyQuery(f, term.text())
464                     .fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits())).boost(b * boost));
465         } else if (isSearchField(field)) {
466             context.addFieldLog(field, term.text());
467             return QueryBuilders.fuzzyQuery(field, term.text()).boost(boost).fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits()));
468         } else {
469             final String origQuery = fuzzyQuery.toString();
470             context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
471             context.addHighlightedQuery(origQuery);
472             return buildDefaultQueryBuilder((f, b) -> QueryBuilders.fuzzyQuery(f, origQuery)
473                     .fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits())).boost(b * boost));
474         }
475     }
476 
477     protected QueryBuilder convertTermRangeQuery(final QueryContext context, final TermRangeQuery termRangeQuery, final float boost) {
478         final String field = termRangeQuery.getField();
479         if (isSearchField(field)) {
480             context.addFieldLog(field, termRangeQuery.toString(field));
481             final RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(field);
482             final BytesRef min = termRangeQuery.getLowerTerm();
483             if (min != null) {
484                 if (termRangeQuery.includesLower()) {
485                     rangeQuery.gte(min.utf8ToString());
486                 } else {
487                     rangeQuery.gt(min.utf8ToString());
488                 }
489             }
490             final BytesRef max = termRangeQuery.getUpperTerm();
491             if (max != null) {
492                 if (termRangeQuery.includesUpper()) {
493                     rangeQuery.lte(max.utf8ToString());
494                 } else {
495                     rangeQuery.lt(max.utf8ToString());
496                 }
497             }
498             rangeQuery.boost(boost);
499             return rangeQuery;
500         } else {
501             final String origQuery = termRangeQuery.toString();
502             context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
503             context.addHighlightedQuery(origQuery);
504             return buildDefaultQueryBuilder((f, b) -> QueryBuilders.matchPhraseQuery(f, origQuery).boost(b));
505         }
506     }
507 
508     protected QueryBuilder convertTermQuery(final QueryContext context, final TermQuery termQuery, final float boost) {
509         final String field = termQuery.getTerm().field();
510         final String text = termQuery.getTerm().text();
511         if (fessConfig.getQueryReplaceTermWithPrefixQueryAsBoolean() && text.length() > 1 && text.endsWith("*")) {
512             return convertPrefixQuery(context, new PrefixQuery(new Term(field, text.substring(0, text.length() - 1))), boost);
513         } else if (Constants.DEFAULT_FIELD.equals(field)) {
514             context.addFieldLog(field, text);
515             context.addHighlightedQuery(text);
516             return buildDefaultQueryBuilder((f, b) -> QueryBuilders.matchPhraseQuery(f, text).boost(b * boost));
517         } else if ("sort".equals(field)) {
518             final String[] values = text.split("\\.");
519             if (values.length > 2) {
520                 throw new InvalidQueryException(
521                         messages -> messages.addErrorsInvalidQuerySortValue(UserMessages.GLOBAL_PROPERTY_KEY, text), "Invalid sort field: "
522                                 + termQuery);
523             }
524             final String sortField = values[0];
525             if (!isSortField(sortField)) {
526                 throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnsupportedSortField(
527                         UserMessages.GLOBAL_PROPERTY_KEY, sortField), "Unsupported sort field: " + termQuery);
528             }
529             SortOrder sortOrder;
530             if (values.length == 2) {
531                 sortOrder = SortOrder.DESC.toString().equalsIgnoreCase(values[1]) ? SortOrder.DESC : SortOrder.ASC;
532                 if (sortOrder == null) {
533                     throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnsupportedSortOrder(
534                             UserMessages.GLOBAL_PROPERTY_KEY, values[1]), "Invalid sort order: " + termQuery);
535                 }
536             } else {
537                 sortOrder = SortOrder.ASC;
538             }
539             context.addSorts(createFieldSortBuilder(sortField, sortOrder));
540             return null;
541         } else if (INURL_FIELD.equals(field)) {
542             return QueryBuilders.wildcardQuery(fessConfig.getIndexFieldUrl(), "*" + text + "*").boost(boost);
543         } else if (isSearchField(field)) {
544             context.addFieldLog(field, text);
545             context.addHighlightedQuery(text);
546             if (notAnalyzedFieldSet.contains(field)) {
547                 return QueryBuilders.termQuery(field, text).boost(boost);
548             } else {
549                 return QueryBuilders.matchPhraseQuery(field, text).boost(boost);
550             }
551         } else {
552             final String origQuery = termQuery.toString();
553             context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
554             context.addHighlightedQuery(origQuery);
555             return buildDefaultQueryBuilder((f, b) -> QueryBuilders.matchPhraseQuery(f, origQuery).boost(b * boost));
556         }
557     }
558 
559     private QueryBuilder convertPhraseQuery(final QueryContext context, final PhraseQuery query, final float boost) {
560         final Term[] terms = query.getTerms();
561         if (terms.length == 0) {
562             throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
563                     "Unknown phrase query: " + query);
564         }
565         final String field = terms[0].field();
566         final String[] texts = stream(terms).get(stream -> stream.map(term -> term.text()).toArray(n -> new String[n]));
567         final String text = String.join(" ", texts);
568         context.addFieldLog(field, text);
569         stream(texts).of(stream -> stream.forEach(t -> context.addHighlightedQuery(t)));
570         return buildDefaultQueryBuilder((f, b) -> QueryBuilders.matchPhraseQuery(f, text).boost(b * boost));
571     }
572 
573     private boolean isSearchField(final String field) {
574         for (final String searchField : searchFields) {
575             if (searchField.equals(field)) {
576                 return true;
577             }
578         }
579         return false;
580     }
581 
582     private QueryBuilder buildDefaultQueryBuilder(final DefaultQueryBuilderFunction builder) {
583         final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
584         final QueryBuilder titleQuery =
585                 builder.apply(fessConfig.getIndexFieldTitle(), fessConfig.getQueryBoostTitleAsDecimal().floatValue());
586         boolQuery.should(titleQuery);
587         final QueryBuilder contentQuery =
588                 builder.apply(fessConfig.getIndexFieldContent(), fessConfig.getQueryBoostContentAsDecimal().floatValue());
589         boolQuery.should(contentQuery);
590         getQueryLanguages().ifPresent(
591                 langs -> stream(langs).of(
592                         stream -> stream.forEach(lang -> {
593                             final QueryBuilder titleLangQuery =
594                                     builder.apply(fessConfig.getIndexFieldTitle() + "_" + lang, fessConfig
595                                             .getQueryBoostTitleLangAsDecimal().floatValue());
596                             boolQuery.should(titleLangQuery);
597                             final QueryBuilder contentLangQuery =
598                                     builder.apply(fessConfig.getIndexFieldContent() + "_" + lang, fessConfig
599                                             .getQueryBoostContentLangAsDecimal().floatValue());
600                             boolQuery.should(contentLangQuery);
601                         })));
602         return boolQuery;
603     }
604 
605     interface DefaultQueryBuilderFunction {
606         QueryBuilder apply(String field, float boost);
607     }
608 
609     protected OptionalThing<String[]> getQueryLanguages() {
610         return LaRequestUtil.getOptionalRequest()
611                 .map(request -> fessConfig.getQueryLanguages(request.getLocales(),
612                         (String[]) request.getAttribute(Constants.REQUEST_LANGUAGES)));
613     }
614 
615     private boolean isSortField(final String field) {
616         for (final String f : sortFields) {
617             if (f.equals(field)) {
618                 return true;
619             }
620         }
621         return false;
622     }
623 
624     public boolean isFacetField(final String field) {
625         if (StringUtil.isBlank(field)) {
626             return false;
627         }
628         boolean flag = false;
629         for (final String f : facetFields) {
630             if (field.equals(f)) {
631                 flag = true;
632             }
633         }
634         return flag;
635     }
636 
637     public boolean isFacetSortValue(final String sort) {
638         return "count".equals(sort) || "index".equals(sort);
639     }
640 
641     public void setApiResponseFields(final String[] fields) {
642         apiResponseFieldSet = new HashSet<>();
643         for (final String field : fields) {
644             apiResponseFieldSet.add(field);
645         }
646     }
647 
648     public void setNotAnalyzedFields(final String[] fields) {
649         notAnalyzedFieldSet = new HashSet<>();
650         for (final String field : fields) {
651             notAnalyzedFieldSet.add(field);
652         }
653     }
654 
655     public boolean isApiResponseField(final String field) {
656         return apiResponseFieldSet.contains(field);
657     }
658 
659     /**
660      * @return the responseFields
661      */
662     public String[] getResponseFields() {
663         return responseFields;
664     }
665 
666     /**
667      * @param responseFields the responseFields to set
668      */
669     public void setResponseFields(final String[] responseFields) {
670         this.responseFields = responseFields;
671     }
672 
673     public String[] getCacheResponseFields() {
674         return cacheResponseFields;
675     }
676 
677     public void setCacheResponseFields(final String[] cacheResponseFields) {
678         this.cacheResponseFields = cacheResponseFields;
679     }
680 
681     /**
682      * @return the highlightedFields
683      */
684     public String[] getHighlightedFields() {
685         return highlightedFields;
686     }
687 
688     /**
689      * @param highlightedFields the highlightedFields to set
690      */
691     public void setHighlightedFields(final String[] highlightedFields) {
692         this.highlightedFields = highlightedFields;
693     }
694 
695     public void highlightedFields(final Consumer<Stream<String>> stream) {
696         stream(highlightedFields).of(stream);
697     }
698 
699     /**
700      * @return the supportedFields
701      */
702     public String[] getSearchFields() {
703         return searchFields;
704     }
705 
706     /**
707      * @param supportedFields the supportedFields to set
708      */
709     public void setSearchFields(final String[] supportedFields) {
710         searchFields = supportedFields;
711     }
712 
713     /**
714      * @return the facetFields
715      */
716     public String[] getFacetFields() {
717         return facetFields;
718     }
719 
720     /**
721      * @param facetFields the facetFields to set
722      */
723     public void setFacetFields(final String[] facetFields) {
724         this.facetFields = facetFields;
725     }
726 
727     /**
728      * @return the sortPrefix
729      */
730     public String getSortPrefix() {
731         return sortPrefix;
732     }
733 
734     /**
735      * @param sortPrefix the sortPrefix to set
736      */
737     public void setSortPrefix(final String sortPrefix) {
738         this.sortPrefix = sortPrefix;
739     }
740 
741     /**
742      * @return the sortFields
743      */
744     public String[] getSortFields() {
745         return sortFields;
746     }
747 
748     /**
749      * @param sortFields the sortFields to set
750      */
751     public void setSortFields(final String[] sortFields) {
752         this.sortFields = sortFields;
753     }
754 
755     public void addHighlightField(final String field) {
756         highlightFieldSet.add(field);
757     }
758 
759     /**
760      * @return the additionalQuery
761      */
762     public String getAdditionalQuery() {
763         return additionalQuery;
764     }
765 
766     /**
767      * @param additionalQuery the additionalQuery to set
768      */
769     public void setAdditionalQuery(final String additionalQuery) {
770         this.additionalQuery = additionalQuery;
771     }
772 
773     public void addDefaultSort(final String fieldName, final String order) {
774         final List<SortBuilder<?>> list = new ArrayList<>();
775         if (defaultSortBuilders != null) {
776             stream(defaultSortBuilders).of(stream -> stream.forEach(builder -> list.add(builder)));
777         }
778         list.add(createFieldSortBuilder(fieldName, SortOrder.DESC.toString().equalsIgnoreCase(order) ? SortOrder.DESC : SortOrder.ASC));
779         defaultSortBuilders = list.toArray(new SortBuilder[list.size()]);
780     }
781 
782     protected SortBuilder<?> createFieldSortBuilder(final String field, final SortOrder order) {
783         if (SCORE_FIELD.equals(field) || ES_SCORE_FIELD.equals(field)) {
784             return SortBuilders.scoreSort().order(order);
785         } else {
786             return SortBuilders.fieldSort(field).order(order);
787         }
788     }
789 
790     public void setHighlightPrefix(final String highlightPrefix) {
791         this.highlightPrefix = highlightPrefix;
792     }
793 
794     public String getHighlightPrefix() {
795         return highlightPrefix;
796     }
797 
798     public FacetInfo getDefaultFacetInfo() {
799         return defaultFacetInfo;
800     }
801 
802     public void setDefaultFacetInfo(final FacetInfo defaultFacetInfo) {
803         this.defaultFacetInfo = defaultFacetInfo;
804     }
805 
806     public GeoInfo getDefaultGeoInfo() {
807         return defaultGeoInfo;
808     }
809 
810     public void setDefaultGeoInfo(final GeoInfo defaultGeoInfo) {
811         this.defaultGeoInfo = defaultGeoInfo;
812     }
813 
814     public String generateId() {
815         return UUID.randomUUID().toString().replace("-", StringUtil.EMPTY);
816     }
817 
818     public void setLowercaseWildcard(final boolean lowercaseWildcard) {
819         this.lowercaseWildcard = lowercaseWildcard;
820     }
821 
822     public void addBoostFunction(final ScoreFunctionBuilder<?> scoreFunction) {
823         boostFunctionList.add(new FilterFunctionBuilder(scoreFunction));
824     }
825 
826     public void addBoostFunction(final QueryBuilder filter, final ScoreFunctionBuilder<?> scoreFunction) {
827         boostFunctionList.add(new FilterFunctionBuilder(filter, scoreFunction));
828     }
829 }