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.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.UUID;
26 import java.util.function.Consumer;
27
28 import org.apache.logging.log4j.LogManager;
29 import org.apache.logging.log4j.Logger;
30 import org.apache.lucene.search.Query;
31 import org.codelibs.core.lang.StringUtil;
32 import org.codelibs.fess.Constants;
33 import org.codelibs.fess.entity.FacetInfo;
34 import org.codelibs.fess.entity.GeoInfo;
35 import org.codelibs.fess.entity.QueryContext;
36 import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
37 import org.codelibs.fess.exception.InvalidQueryException;
38 import org.codelibs.fess.exception.QueryParseException;
39 import org.codelibs.fess.mylasta.action.FessUserBean;
40 import org.codelibs.fess.mylasta.direction.FessConfig;
41 import org.codelibs.fess.query.QueryFieldConfig;
42 import org.codelibs.fess.query.parser.QueryParser;
43 import org.codelibs.fess.score.QueryRescorer;
44 import org.codelibs.fess.util.ComponentUtil;
45 import org.dbflute.optional.OptionalThing;
46 import org.lastaflute.core.message.UserMessages;
47 import org.lastaflute.web.util.LaRequestUtil;
48 import org.opensearch.action.search.SearchRequestBuilder;
49 import org.opensearch.index.query.BoolQueryBuilder;
50 import org.opensearch.index.query.QueryBuilder;
51 import org.opensearch.index.query.QueryBuilders;
52 import org.opensearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder;
53 import org.opensearch.index.query.functionscore.ScoreFunctionBuilder;
54 import org.opensearch.index.query.functionscore.ScoreFunctionBuilders;
55 import org.opensearch.search.rescore.RescorerBuilder;
56 import org.opensearch.search.sort.SortBuilder;
57 import org.opensearch.search.sort.SortBuilders;
58 import org.opensearch.search.sort.SortOrder;
59
60 import jakarta.servlet.http.HttpServletRequest;
61 import jakarta.servlet.http.HttpSession;
62
63
64
65
66
67
68
69 public class QueryHelper {
70
71
72
73
74 public QueryHelper() {
75
76 }
77
78
79 private static final Logger logger = LogManager.getLogger(QueryHelper.class);
80
81
82 protected static final String PREFERENCE_QUERY = "_query";
83
84
85 protected String sortPrefix = "sort:";
86
87
88 protected String additionalQuery;
89
90
91 protected SortBuilder<?>[] defaultSortBuilders;
92
93
94 protected String highlightPrefix = "hl_";
95
96
97 protected FacetInfo defaultFacetInfo;
98
99
100 protected GeoInfo defaultGeoInfo;
101
102
103 protected Map<String, String> fieldBoostMap = new HashMap<>();
104
105
106 protected List<FilterFunctionBuilder> boostFunctionList = new ArrayList<>();
107
108
109 protected List<QueryRescorer> queryRescorerList = new ArrayList<>();
110
111
112
113
114
115
116
117
118
119
120 public QueryContext build(final SearchRequestType searchRequestType, final String query, final Consumer<QueryContext> context) {
121 String q;
122 if (additionalQuery != null && StringUtil.isNotBlank(query)) {
123 q = query + " " + additionalQuery;
124 } else {
125 q = query;
126 }
127
128 final QueryContext queryContext = new QueryContext(q, true);
129 buildBaseQuery(queryContext, context);
130 buildBoostQuery(queryContext);
131 buildRoleQuery(queryContext, searchRequestType);
132 buildVirtualHostQuery(queryContext, searchRequestType);
133
134 if (!queryContext.hasSorts() && defaultSortBuilders != null) {
135 queryContext.addSorts(defaultSortBuilders);
136 }
137 return queryContext;
138 }
139
140
141
142
143
144
145
146
147 protected void buildVirtualHostQuery(final QueryContext queryContext, final SearchRequestType searchRequestType) {
148 switch (searchRequestType) {
149 case ADMIN_SEARCH:
150
151 break;
152 default:
153 final String key = ComponentUtil.getVirtualHostHelper().getVirtualHostKey();
154 if (StringUtil.isNotBlank(key)) {
155 queryContext.addQuery(boolQuery -> {
156 boolQuery.filter(QueryBuilders.termQuery(ComponentUtil.getFessConfig().getIndexFieldVirtualHost(), key));
157 });
158 }
159 break;
160 }
161 }
162
163
164
165
166
167
168
169
170 protected void buildRoleQuery(final QueryContext queryContext, final SearchRequestType searchRequestType) {
171 if (queryContext.roleQueryEnabled()) {
172 final Set<String> roleSet = ComponentUtil.getRoleQueryHelper().build(searchRequestType);
173 if (!roleSet.isEmpty()) {
174 queryContext.addQuery(boolQuery -> buildRoleQuery(roleSet, boolQuery));
175 }
176 }
177 }
178
179
180
181
182
183
184
185
186 public void buildRoleQuery(final Set<String> roleSet, final BoolQueryBuilder boolQuery) {
187 final BoolQueryBuilder roleQuery = QueryBuilders.boolQuery();
188 final FessConfig fessConfig = ComponentUtil.getFessConfig();
189 final String roleField = fessConfig.getIndexFieldRole();
190 roleSet.stream().forEach(name -> roleQuery.should(QueryBuilders.termQuery(roleField, name)));
191 final String deniedPrefix = fessConfig.getRoleSearchDeniedPrefix();
192 roleSet.stream().forEach(name -> roleQuery.mustNot(QueryBuilders.termQuery(roleField, deniedPrefix + name)));
193 boolQuery.filter(roleQuery);
194 }
195
196
197
198
199
200
201
202 protected void buildBoostQuery(final QueryContext queryContext) {
203 queryContext.addFunctionScore(list -> {
204 list.add(new FilterFunctionBuilder(
205 ScoreFunctionBuilders.fieldValueFactorFunction(ComponentUtil.getFessConfig().getIndexFieldBoost())));
206 ComponentUtil.getKeyMatchHelper().buildQuery(queryContext.getDefaultKeyword(), list);
207 list.addAll(boostFunctionList);
208 });
209 }
210
211
212
213
214
215
216
217
218
219 public void buildBaseQuery(final QueryContext queryContext, final Consumer<QueryContext> context) {
220 try {
221 final Query query = getQueryParser().parse(queryContext.getQueryString());
222 final QueryBuilder queryBuilder = ComponentUtil.getQueryProcessor().execute(queryContext, query, 1.0f);
223 if (queryBuilder != null) {
224 queryContext.setQueryBuilder(queryBuilder);
225 } else {
226 queryContext.setQueryBuilder(QueryBuilders.matchAllQuery());
227 }
228
229 context.accept(queryContext);
230 } catch (final QueryParseException e) {
231 throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryParseError(UserMessages.GLOBAL_PROPERTY_KEY),
232 "Invalid query: " + queryContext.getQueryString(), e);
233 }
234 }
235
236
237
238
239
240
241 protected QueryParser getQueryParser() {
242 return ComponentUtil.getQueryParser();
243 }
244
245
246
247
248
249
250
251
252
253 public void processSearchPreference(final SearchRequestBuilder searchRequestBuilder, final OptionalThing<FessUserBean> userBean,
254 final String query) {
255 userBean.map(user -> {
256 if (user.hasRoles(ComponentUtil.getFessConfig().getAuthenticationAdminRolesAsArray())) {
257 return Constants.SEARCH_PREFERENCE_LOCAL;
258 }
259 return user.getUserId();
260 }).ifPresent(p -> searchRequestBuilder.setPreference(p)).orElse(() -> LaRequestUtil.getOptionalRequest().map(r -> {
261 final HttpSession session = r.getSession(false);
262 if (session != null) {
263 return session.getId();
264 }
265 final String preference = r.getParameter("preference");
266 if (preference != null) {
267 return Integer.toString(preference.hashCode());
268 }
269 final Object accessType = r.getAttribute(Constants.SEARCH_LOG_ACCESS_TYPE);
270 if (Constants.SEARCH_LOG_ACCESS_TYPE_JSON.equals(accessType)) {
271 return processJsonSearchPreference(r, query);
272 }
273 if (Constants.SEARCH_LOG_ACCESS_TYPE_GSA.equals(accessType)) {
274 return processGsaSearchPreference(r, query);
275 }
276 return null;
277 }).ifPresent(p -> searchRequestBuilder.setPreference(p)));
278 }
279
280
281
282
283
284
285
286
287
288 protected String processJsonSearchPreference(final HttpServletRequest req, final String query) {
289 final String pref = ComponentUtil.getFessConfig().getQueryJsonDefaultPreference();
290 if (PREFERENCE_QUERY.equals(pref)) {
291 return Integer.toString(query.hashCode());
292 }
293 if (StringUtil.isNotBlank(pref)) {
294 return pref;
295 }
296 return null;
297 }
298
299
300
301
302
303
304
305
306
307 protected String processGsaSearchPreference(final HttpServletRequest req, final String query) {
308 final String pref = ComponentUtil.getFessConfig().getQueryGsaDefaultPreference();
309 if (PREFERENCE_QUERY.equals(pref)) {
310 return Integer.toString(query.hashCode());
311 }
312 if (StringUtil.isNotBlank(pref)) {
313 return pref;
314 }
315 return null;
316 }
317
318
319
320
321
322
323 public String getSortPrefix() {
324 return sortPrefix;
325 }
326
327
328
329
330
331
332 public void setSortPrefix(final String sortPrefix) {
333 this.sortPrefix = sortPrefix;
334 }
335
336
337
338
339
340
341 public String getAdditionalQuery() {
342 return additionalQuery;
343 }
344
345
346
347
348
349
350 public void setAdditionalQuery(final String additionalQuery) {
351 this.additionalQuery = additionalQuery;
352 }
353
354
355
356
357
358
359
360
361 public void addDefaultSort(final String fieldName, final String order) {
362 final List<SortBuilder<?>> list = new ArrayList<>();
363 if (defaultSortBuilders != null) {
364 stream(defaultSortBuilders).of(stream -> stream.forEach(builder -> list.add(builder)));
365 }
366 list.add(createFieldSortBuilder(fieldName, SortOrder.DESC.toString().equalsIgnoreCase(order) ? SortOrder.DESC : SortOrder.ASC));
367 defaultSortBuilders = list.toArray(new SortBuilder[list.size()]);
368 }
369
370
371
372
373
374
375
376
377
378 protected SortBuilder<?> createFieldSortBuilder(final String field, final SortOrder order) {
379 if (QueryFieldConfig.SCORE_FIELD.equals(field) || QueryFieldConfig.DOC_SCORE_FIELD.equals(field)) {
380 return SortBuilders.scoreSort().order(order);
381 }
382 return SortBuilders.fieldSort(field).order(order);
383 }
384
385
386
387
388
389
390 public void setHighlightPrefix(final String highlightPrefix) {
391 this.highlightPrefix = highlightPrefix;
392 }
393
394
395
396
397
398
399 public String getHighlightPrefix() {
400 return highlightPrefix;
401 }
402
403
404
405
406
407
408 public FacetInfo getDefaultFacetInfo() {
409 return defaultFacetInfo;
410 }
411
412
413
414
415
416
417 public void setDefaultFacetInfo(final FacetInfo defaultFacetInfo) {
418 this.defaultFacetInfo = defaultFacetInfo;
419 }
420
421
422
423
424
425
426 public GeoInfo getDefaultGeoInfo() {
427 return defaultGeoInfo;
428 }
429
430
431
432
433
434
435 public void setDefaultGeoInfo(final GeoInfo defaultGeoInfo) {
436 this.defaultGeoInfo = defaultGeoInfo;
437 }
438
439
440
441
442
443
444 public String generateId() {
445 return UUID.randomUUID().toString().replace("-", StringUtil.EMPTY);
446 }
447
448
449
450
451
452
453
454 public void addBoostFunction(final ScoreFunctionBuilder<?> scoreFunction) {
455 boostFunctionList.add(new FilterFunctionBuilder(scoreFunction));
456 }
457
458
459
460
461
462
463
464
465 public void addBoostFunction(final QueryBuilder filter, final ScoreFunctionBuilder<?> scoreFunction) {
466 boostFunctionList.add(new FilterFunctionBuilder(filter, scoreFunction));
467 }
468
469
470
471
472
473
474
475
476 public RescorerBuilder<?>[] getRescorers(final Map<String, Object> params) {
477 return queryRescorerList.stream().map(r -> r.evaluate(params)).filter(b -> b != null).toArray(n -> new RescorerBuilder<?>[n]);
478 }
479
480
481
482
483
484
485
486 public void addQueryRescorer(final QueryRescorer rescorer) {
487 queryRescorerList.add(rescorer);
488 }
489 }