1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.rank.fusion;
17
18 import java.util.HashMap;
19 import java.util.Map;
20
21 import org.apache.logging.log4j.LogManager;
22 import org.apache.logging.log4j.Logger;
23 import org.codelibs.core.collection.ArrayUtil;
24 import org.codelibs.core.stream.StreamUtil;
25 import org.codelibs.fess.Constants;
26 import org.codelibs.fess.entity.SearchRequestParams;
27 import org.codelibs.fess.helper.ViewHelper;
28 import org.codelibs.fess.mylasta.action.FessUserBean;
29 import org.codelibs.fess.mylasta.direction.FessConfig;
30 import org.codelibs.fess.opensearch.client.SearchEngineClient.SearchCondition;
31 import org.codelibs.fess.opensearch.client.SearchEngineClient.SearchConditionBuilder;
32 import org.codelibs.fess.rank.fusion.SearchResult.SearchResultBuilder;
33 import org.codelibs.fess.util.ComponentUtil;
34 import org.codelibs.fess.util.DocumentUtil;
35 import org.codelibs.fess.util.FacetResponse;
36 import org.dbflute.optional.OptionalEntity;
37 import org.dbflute.optional.OptionalThing;
38 import org.lastaflute.web.util.LaRequestUtil;
39 import org.opensearch.action.search.SearchRequestBuilder;
40 import org.opensearch.action.search.SearchResponse;
41 import org.opensearch.common.document.DocumentField;
42 import org.opensearch.search.SearchHit;
43 import org.opensearch.search.SearchHits;
44 import org.opensearch.search.aggregations.Aggregations;
45 import org.opensearch.search.fetch.subphase.highlight.HighlightField;
46
47
48
49
50
51 public class DefaultSearcher extends RankFusionSearcher {
52
53
54 private static final Logger logger = LogManager.getLogger(DefaultSearcher.class);
55
56
57
58
59
60
61 public DefaultSearcher() {
62 super();
63 }
64
65
66
67
68
69
70
71
72
73 @Override
74 protected SearchResult search(final String query, final SearchRequestParams params, final OptionalThing<FessUserBean> userBean) {
75 final int pageSize = params.getPageSize();
76 LaRequestUtil.getOptionalRequest().ifPresent(request -> {
77 request.setAttribute(Constants.REQUEST_PAGE_SIZE, pageSize);
78 });
79 final OptionalEntity<SearchResponse> searchResponseOpt = sendRequest(query, params, userBean);
80 return processResponse(searchResponseOpt);
81 }
82
83
84
85
86
87
88
89 protected SearchResult processResponse(final OptionalEntity<SearchResponse> searchResponseOpt) {
90 final FessConfig fessConfig = ComponentUtil.getFessConfig();
91 final SearchResultBuilder builder = SearchResult.create();
92 searchResponseOpt.ifPresent(searchResponse -> {
93 final SearchHits searchHits = searchResponse.getHits();
94 builder.allRecordCount(searchHits.getTotalHits().value());
95 builder.allRecordCountRelation(searchHits.getTotalHits().relation().toString());
96 builder.queryTime(searchResponse.getTook().millis());
97
98 if (searchResponse.getTotalShards() != searchResponse.getSuccessfulShards()) {
99 builder.partialResults(true);
100 }
101
102
103 final String hlPrefix = ComponentUtil.getQueryHelper().getHighlightPrefix();
104 for (final SearchHit searchHit : searchHits.getHits()) {
105 final Map<String, Object> docMap = parseSearchHit(fessConfig, hlPrefix, searchHit);
106
107 if (fessConfig.isResultCollapsed()) {
108 final Map<String, SearchHits> innerHits = searchHit.getInnerHits();
109 if (innerHits != null) {
110 final SearchHits innerSearchHits = innerHits.get(fessConfig.getQueryCollapseInnerHitsName());
111 if (innerSearchHits != null) {
112 final long totalHits = innerSearchHits.getTotalHits().value();
113 if (totalHits > 1) {
114 docMap.put(fessConfig.getQueryCollapseInnerHitsName() + "_count", totalHits);
115 final DocumentField bitsField = searchHit.getFields().get(fessConfig.getIndexFieldContentMinhashBits());
116 if (bitsField != null && !bitsField.getValues().isEmpty()) {
117 docMap.put(fessConfig.getQueryCollapseInnerHitsName() + "_hash", bitsField.getValues().get(0));
118 }
119 docMap.put(fessConfig.getQueryCollapseInnerHitsName(), StreamUtil.stream(innerSearchHits.getHits())
120 .get(stream -> stream.map(hit -> parseSearchHit(fessConfig, hlPrefix, hit)).toArray(Map[]::new)));
121 }
122 }
123 }
124 }
125
126 builder.addDocument(docMap);
127 }
128
129
130 final Aggregations aggregations = searchResponse.getAggregations();
131 if (aggregations != null) {
132 builder.facetResponse(new FacetResponse(aggregations));
133 }
134
135 });
136 return builder.build();
137 }
138
139
140
141
142
143
144
145
146
147 protected OptionalEntity<SearchResponse> sendRequest(final String query, final SearchRequestParams params,
148 final OptionalThing<FessUserBean> userBean) {
149 final FessConfig fessConfig = ComponentUtil.getFessConfig();
150 return ComponentUtil.getSearchEngineClient()
151 .search(fessConfig.getIndexDocumentSearchIndex(), createSearchCondition(query, params, userBean),
152 (searchRequestBuilder, execTime, searchResponse) -> {
153 searchResponse.ifPresent(r -> {
154 if (r.getTotalShards() != r.getSuccessfulShards() && fessConfig.isQueryTimeoutLogging()) {
155
156 final StringBuilder buf = new StringBuilder(1000);
157 buf.append("[SEARCH TIMEOUT] {\"exec_time\":")
158 .append(execTime)
159 .append(",\"request\":")
160 .append(searchRequestBuilder.toString())
161 .append(",\"response\":")
162 .append(r.toString())
163 .append('}');
164 logger.warn(buf.toString());
165 }
166 });
167 return searchResponse;
168 });
169 }
170
171
172
173
174
175
176
177
178
179 protected SearchCondition<SearchRequestBuilder> createSearchCondition(final String query, final SearchRequestParams params,
180 final OptionalThing<FessUserBean> userBean) {
181 return searchRequestBuilder -> {
182 ComponentUtil.getQueryHelper().processSearchPreference(searchRequestBuilder, userBean, query);
183 return SearchConditionBuilder.builder(searchRequestBuilder)
184 .query(query)
185 .offset(params.getStartPosition())
186 .size(params.getPageSize())
187 .facetInfo(params.getFacetInfo())
188 .geoInfo(params.getGeoInfo())
189 .highlightInfo(params.getHighlightInfo())
190 .similarDocHash(params.getSimilarDocHash())
191 .responseFields(params.getResponseFields())
192 .searchRequestType(params.getType())
193 .trackTotalHits(params.getTrackTotalHits())
194 .minScore(params.getMinScore())
195 .build();
196 };
197 }
198
199
200
201
202
203
204
205
206
207 protected Map<String, Object> parseSearchHit(final FessConfig fessConfig, final String hlPrefix, final SearchHit searchHit) {
208 final Map<String, Object> docMap = new HashMap<>(32);
209 if (searchHit.getSourceAsMap() == null) {
210 searchHit.getFields().forEach((key, value) -> {
211 docMap.put(key, value.getValue());
212 });
213 } else {
214 docMap.putAll(searchHit.getSourceAsMap());
215 }
216
217 final ViewHelper viewHelper = ComponentUtil.getViewHelper();
218
219 final Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
220 try {
221 if (highlightFields != null) {
222 highlightFields.values().stream().forEach(highlightField -> {
223 final String text = viewHelper.createHighlightText(highlightField);
224 if (text != null) {
225 docMap.put(hlPrefix + highlightField.getName(), text);
226 }
227 });
228 if (Constants.TEXT_FRAGMENT_TYPE_HIGHLIGHT.equals(fessConfig.getQueryHighlightTextFragmentType())) {
229 docMap.put(Constants.TEXT_FRAGMENTS,
230 viewHelper.createTextFragmentsByHighlight(highlightFields.values().toArray(HighlightField[]::new)));
231 }
232 }
233 } catch (final Exception e) {
234 if (logger.isDebugEnabled()) {
235 logger.debug("Could not create a highlighting value: {}", docMap, e);
236 }
237 }
238
239 if (Constants.TEXT_FRAGMENT_TYPE_QUERY.equals(fessConfig.getQueryHighlightTextFragmentType())) {
240 docMap.put(Constants.TEXT_FRAGMENTS, viewHelper.createTextFragmentsByQuery());
241 }
242
243
244 if (viewHelper != null) {
245 docMap.put(fessConfig.getResponseFieldContentTitle(), viewHelper.getContentTitle(docMap));
246 docMap.put(fessConfig.getResponseFieldContentDescription(), viewHelper.getContentDescription(docMap));
247 docMap.put(fessConfig.getResponseFieldUrlLink(), viewHelper.getUrlLink(docMap));
248 docMap.put(fessConfig.getResponseFieldSitePath(), viewHelper.getSitePath(docMap));
249 }
250
251 if (!docMap.containsKey(Constants.SCORE)) {
252 final float score = searchHit.getScore();
253 if (Float.isFinite(score)) {
254 docMap.put(Constants.SCORE, score);
255 }
256 }
257
258 if (!docMap.containsKey(fessConfig.getIndexFieldId())) {
259 docMap.put(fessConfig.getIndexFieldId(), searchHit.getId());
260 }
261
262 final String[] searchers = DocumentUtil.getValue(docMap, Constants.SEARCHER, String[].class);
263 if (searchers != null) {
264 docMap.put(Constants.SEARCHER, ArrayUtil.add(searchers, getName()));
265 } else {
266 docMap.put(Constants.SEARCHER, new String[] { getName() });
267 }
268
269 return docMap;
270 }
271
272 }