1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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
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
661
662 public String[] getResponseFields() {
663 return responseFields;
664 }
665
666
667
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
683
684 public String[] getHighlightedFields() {
685 return highlightedFields;
686 }
687
688
689
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
701
702 public String[] getSearchFields() {
703 return searchFields;
704 }
705
706
707
708
709 public void setSearchFields(final String[] supportedFields) {
710 searchFields = supportedFields;
711 }
712
713
714
715
716 public String[] getFacetFields() {
717 return facetFields;
718 }
719
720
721
722
723 public void setFacetFields(final String[] facetFields) {
724 this.facetFields = facetFields;
725 }
726
727
728
729
730 public String getSortPrefix() {
731 return sortPrefix;
732 }
733
734
735
736
737 public void setSortPrefix(final String sortPrefix) {
738 this.sortPrefix = sortPrefix;
739 }
740
741
742
743
744 public String[] getSortFields() {
745 return sortFields;
746 }
747
748
749
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
761
762 public String getAdditionalQuery() {
763 return additionalQuery;
764 }
765
766
767
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 }