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