1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.util;
17
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.HashMap;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.ListIterator;
24 import java.util.Map;
25
26 import org.apache.commons.lang3.StringUtils;
27 import org.codelibs.core.stream.StreamUtil;
28 import org.codelibs.fess.helper.QueryHelper;
29 import org.codelibs.fess.helper.ViewHelper;
30 import org.codelibs.fess.mylasta.direction.FessConfig;
31 import org.dbflute.optional.OptionalEntity;
32 import org.elasticsearch.action.search.SearchResponse;
33 import org.elasticsearch.common.text.Text;
34 import org.elasticsearch.search.SearchHit;
35 import org.elasticsearch.search.SearchHitField;
36 import org.elasticsearch.search.SearchHits;
37 import org.elasticsearch.search.aggregations.Aggregations;
38 import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 public class QueryResponseList implements List<Map<String, Object>> {
43
44 private static final String SCORE = "score";
45
46 private static final Logger logger = LoggerFactory.getLogger(QueryResponseList.class);
47
48 private final List<Map<String, Object>> parent;
49
50
51 protected int pageSize;
52
53
54 protected int currentPageNumber;
55
56 protected long allRecordCount;
57
58 protected int allPageCount;
59
60 protected boolean existNextPage;
61
62 protected boolean existPrevPage;
63
64 protected long currentStartRecordNumber;
65
66 protected long currentEndRecordNumber;
67
68 protected List<String> pageNumberList;
69
70 protected String searchQuery;
71
72 protected long execTime;
73
74 protected FacetResponse facetResponse;
75
76 protected boolean partialResults = false;
77
78 protected long queryTime;
79
80 public QueryResponseList() {
81 parent = new ArrayList<>();
82 }
83
84
85 protected QueryResponseList(final List<Map<String, Object>> parent) {
86 this.parent = parent;
87 }
88
89 public void init(final OptionalEntity<SearchResponse> searchResponseOpt, final int start, final int pageSize) {
90 searchResponseOpt.ifPresent(searchResponse -> {
91 final FessConfig fessConfig = ComponentUtil.getFessConfig();
92 final SearchHits searchHits = searchResponse.getHits();
93 allRecordCount = searchHits.getTotalHits();
94 queryTime = searchResponse.getTookInMillis();
95
96 if (searchResponse.getTotalShards() != searchResponse.getSuccessfulShards()) {
97 partialResults = true;
98 }
99
100
101 final QueryHelper queryHelper = ComponentUtil.getQueryHelper();
102 final String hlPrefix = queryHelper.getHighlightPrefix();
103 for (final SearchHit searchHit : searchHits.getHits()) {
104 final Map<String, Object> docMap = parseSearchHit(fessConfig, hlPrefix, searchHit);
105
106 if (fessConfig.isResultCollapsed()) {
107 final Map<String, SearchHits> innerHits = searchHit.getInnerHits();
108 if (innerHits != null) {
109 final SearchHits innerSearchHits = innerHits.get(fessConfig.getQueryCollapseInnerHitsName());
110 if (innerSearchHits != null) {
111 final long totalHits = innerSearchHits.getTotalHits();
112 if (totalHits > 1) {
113 docMap.put(fessConfig.getQueryCollapseInnerHitsName() + "_count", totalHits);
114 final SearchHitField bitsField =
115 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(
120 fessConfig.getQueryCollapseInnerHitsName(),
121 StreamUtil.stream(innerSearchHits.getHits()).get(
122 stream -> stream.map(v -> parseSearchHit(fessConfig, hlPrefix, v)).toArray(
123 n -> new Map[n])));
124 }
125 }
126 }
127 }
128
129 parent.add(docMap);
130 }
131
132
133 final Aggregations aggregations = searchResponse.getAggregations();
134 if (aggregations != null) {
135 facetResponse = new FacetResponse(aggregations);
136 }
137
138 });
139
140 calculatePageInfo(start, pageSize);
141 }
142
143 private Map<String, Object> parseSearchHit(final FessConfig fessConfig, final String hlPrefix, final SearchHit searchHit) {
144 final Map<String, Object> docMap = new HashMap<>();
145 if (searchHit.getSource() == null) {
146 searchHit.getFields().forEach((key, value) -> {
147 docMap.put(key, value.getValue());
148 });
149 } else {
150 docMap.putAll(searchHit.getSource());
151 }
152
153 final Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
154 try {
155 if (highlightFields != null) {
156 for (final Map.Entry<String, HighlightField> entry : highlightFields.entrySet()) {
157 final HighlightField highlightField = entry.getValue();
158 final Text[] fragments = highlightField.fragments();
159 if (fragments != null && fragments.length != 0) {
160 final String[] texts = new String[fragments.length];
161 for (int i = 0; i < fragments.length; i++) {
162 texts[i] = fragments[i].string();
163 }
164 final String value = StringUtils.join(texts, "...");
165 docMap.put(hlPrefix + highlightField.getName(), value);
166 }
167 }
168 }
169 } catch (final Exception e) {
170 if (logger.isDebugEnabled()) {
171 logger.debug("Could not create a highlighting value: " + docMap, e);
172 }
173 }
174
175
176 final ViewHelper viewHelper = ComponentUtil.getViewHelper();
177 if (viewHelper != null) {
178 docMap.put(fessConfig.getResponseFieldContentTitle(), viewHelper.getContentTitle(docMap));
179 docMap.put(fessConfig.getResponseFieldContentDescription(), viewHelper.getContentDescription(docMap));
180 docMap.put(fessConfig.getResponseFieldUrlLink(), viewHelper.getUrlLink(docMap));
181 docMap.put(fessConfig.getResponseFieldSitePath(), viewHelper.getSitePath(docMap));
182 }
183
184 if (!docMap.containsKey(SCORE)) {
185 docMap.put(SCORE, searchHit.getScore());
186 }
187 return docMap;
188 }
189
190 protected void calculatePageInfo(final int start, final int size) {
191 pageSize = size;
192 allPageCount = (int) ((allRecordCount - 1) / pageSize) + 1;
193 existPrevPage = start > 0;
194 existNextPage = start < (long) (allPageCount - 1) * (long) pageSize;
195 currentPageNumber = start / pageSize + 1;
196 if (existNextPage && size() < pageSize) {
197
198 existNextPage = false;
199 allPageCount = currentPageNumber;
200 }
201 currentStartRecordNumber = allRecordCount != 0 ? (currentPageNumber - 1) * pageSize + 1 : 0;
202 currentEndRecordNumber = (long) currentPageNumber * pageSize;
203 currentEndRecordNumber = allRecordCount < currentEndRecordNumber ? allRecordCount : currentEndRecordNumber;
204
205 final int pageRangeSize = 5;
206 int startPageRangeSize = currentPageNumber - pageRangeSize;
207 if (startPageRangeSize < 1) {
208 startPageRangeSize = 1;
209 }
210 int endPageRangeSize = currentPageNumber + pageRangeSize;
211 if (endPageRangeSize > allPageCount) {
212 endPageRangeSize = allPageCount;
213 }
214 pageNumberList = new ArrayList<>();
215 for (int i = startPageRangeSize; i <= endPageRangeSize; i++) {
216 pageNumberList.add(String.valueOf(i));
217 }
218 }
219
220 @Override
221 public boolean add(final Map<String, Object> e) {
222 return parent.add(e);
223 }
224
225 @Override
226 public void add(final int index, final Map<String, Object> element) {
227 parent.add(index, element);
228 }
229
230 @Override
231 public boolean addAll(final Collection<? extends Map<String, Object>> c) {
232 return parent.addAll(c);
233 }
234
235 @Override
236 public boolean addAll(final int index, final Collection<? extends Map<String, Object>> c) {
237 return parent.addAll(index, c);
238 }
239
240 @Override
241 public void clear() {
242 parent.clear();
243 }
244
245 @Override
246 public boolean contains(final Object o) {
247 return parent.contains(o);
248 }
249
250 @Override
251 public boolean containsAll(final Collection<?> c) {
252 return parent.containsAll(c);
253 }
254
255 @Override
256 public boolean equals(final Object o) {
257 return parent.equals(o);
258 }
259
260 @Override
261 public Map<String, Object> get(final int index) {
262 return parent.get(index);
263 }
264
265 @Override
266 public int hashCode() {
267 return parent.hashCode();
268 }
269
270 @Override
271 public int indexOf(final Object o) {
272 return parent.indexOf(o);
273 }
274
275 @Override
276 public boolean isEmpty() {
277 return parent.isEmpty();
278 }
279
280 @Override
281 public Iterator<Map<String, Object>> iterator() {
282 return parent.iterator();
283 }
284
285 @Override
286 public int lastIndexOf(final Object o) {
287 return parent.lastIndexOf(o);
288 }
289
290 @Override
291 public ListIterator<Map<String, Object>> listIterator() {
292 return parent.listIterator();
293 }
294
295 @Override
296 public ListIterator<Map<String, Object>> listIterator(final int index) {
297 return parent.listIterator(index);
298 }
299
300 @Override
301 public Map<String, Object> remove(final int index) {
302 return parent.remove(index);
303 }
304
305 @Override
306 public boolean remove(final Object o) {
307 return parent.remove(o);
308 }
309
310 @Override
311 public boolean removeAll(final Collection<?> c) {
312 return parent.removeAll(c);
313 }
314
315 @Override
316 public boolean retainAll(final Collection<?> c) {
317 return parent.retainAll(c);
318 }
319
320 @Override
321 public Map<String, Object> set(final int index, final Map<String, Object> element) {
322 return parent.set(index, element);
323 }
324
325 @Override
326 public int size() {
327 return parent.size();
328 }
329
330 @Override
331 public List<Map<String, Object>> subList(final int fromIndex, final int toIndex) {
332 return parent.subList(fromIndex, toIndex);
333 }
334
335 @Override
336 public Object[] toArray() {
337 return parent.toArray();
338 }
339
340 @Override
341 public <T> T[] toArray(final T[] a) {
342 return parent.toArray(a);
343 }
344
345 public int getPageSize() {
346 return pageSize;
347 }
348
349 public int getCurrentPageNumber() {
350 return currentPageNumber;
351 }
352
353 public long getAllRecordCount() {
354 return allRecordCount;
355 }
356
357 public int getAllPageCount() {
358 return allPageCount;
359 }
360
361 public boolean isExistNextPage() {
362 return existNextPage;
363 }
364
365 public boolean isExistPrevPage() {
366 return existPrevPage;
367 }
368
369 public long getCurrentStartRecordNumber() {
370 return currentStartRecordNumber;
371 }
372
373 public long getCurrentEndRecordNumber() {
374 return currentEndRecordNumber;
375 }
376
377 public List<String> getPageNumberList() {
378 return pageNumberList;
379 }
380
381 public String getSearchQuery() {
382 return searchQuery;
383 }
384
385 public void setSearchQuery(final String searchQuery) {
386 this.searchQuery = searchQuery;
387 }
388
389 public long getExecTime() {
390 return execTime;
391 }
392
393 public void setExecTime(final long execTime) {
394 this.execTime = execTime;
395 }
396
397 public FacetResponse getFacetResponse() {
398 return facetResponse;
399 }
400
401 public boolean isPartialResults() {
402 return partialResults;
403 }
404
405 public long getQueryTime() {
406 return queryTime;
407 }
408
409 }