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.app.service;
17  
18  import java.nio.charset.StandardCharsets;
19  import java.time.LocalDateTime;
20  import java.time.ZoneId;
21  import java.time.ZoneOffset;
22  import java.time.format.DateTimeFormatter;
23  import java.util.Base64;
24  import java.util.HashMap;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.stream.Collectors;
29  
30  import javax.annotation.Resource;
31  
32  import org.apache.logging.log4j.LogManager;
33  import org.apache.logging.log4j.Logger;
34  import org.codelibs.core.beans.util.BeanUtil;
35  import org.codelibs.core.lang.StringUtil;
36  import org.codelibs.fesen.search.aggregations.AggregationBuilders;
37  import org.codelibs.fesen.search.aggregations.BucketOrder;
38  import org.codelibs.fesen.search.aggregations.bucket.histogram.DateHistogramInterval;
39  import org.codelibs.fesen.search.aggregations.bucket.histogram.Histogram;
40  import org.codelibs.fesen.search.aggregations.bucket.terms.Terms;
41  import org.codelibs.fesen.search.aggregations.metrics.Avg;
42  import org.codelibs.fesen.search.aggregations.metrics.Cardinality;
43  import org.codelibs.fess.Constants;
44  import org.codelibs.fess.app.pager.SearchLogPager;
45  import org.codelibs.fess.es.log.allcommon.EsPagingResultBean;
46  import org.codelibs.fess.es.log.cbean.ClickLogCB;
47  import org.codelibs.fess.es.log.cbean.FavoriteLogCB;
48  import org.codelibs.fess.es.log.cbean.SearchLogCB;
49  import org.codelibs.fess.es.log.cbean.UserInfoCB;
50  import org.codelibs.fess.es.log.exbhv.ClickLogBhv;
51  import org.codelibs.fess.es.log.exbhv.FavoriteLogBhv;
52  import org.codelibs.fess.es.log.exbhv.SearchLogBhv;
53  import org.codelibs.fess.es.log.exbhv.UserInfoBhv;
54  import org.codelibs.fess.es.log.exentity.ClickLog;
55  import org.codelibs.fess.es.log.exentity.FavoriteLog;
56  import org.codelibs.fess.es.log.exentity.SearchLog;
57  import org.codelibs.fess.es.log.exentity.UserInfo;
58  import org.codelibs.fess.exception.FessSystemException;
59  import org.codelibs.fess.helper.SystemHelper;
60  import org.codelibs.fess.mylasta.direction.FessConfig;
61  import org.codelibs.fess.taglib.FessFunctions;
62  import org.dbflute.optional.OptionalEntity;
63  
64  public class SearchLogService {
65  
66      private static final String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm";
67  
68      private static final String COUNT = "count";
69  
70      private static final String KEY = "key";
71  
72      private static final String ID = "id";
73  
74      private static final String USER_INFO_ID = "userInfoId";
75  
76      private static final String QUERY_TIME = "queryTime";
77  
78      private static final Logger logger = LogManager.getLogger(SearchLogService.class);
79  
80      @Resource
81      private SearchLogBhv searchLogBhv;
82  
83      @Resource
84      private ClickLogBhv clickLogBhv;
85  
86      @Resource
87      private FavoriteLogBhv favoriteLogBhv;
88  
89      @Resource
90      private UserInfoBhv userInfoBhv;
91  
92      @Resource
93      private SystemHelper systemHelper;
94  
95      @Resource
96      protected FessConfig fessConfig;
97  
98      public void deleteBefore(final int days) {
99          searchLogBhv.queryDelete(cb -> {
100             cb.query().setRequestedAt_LessEqual(systemHelper.getCurrentTimeAsLocalDateTime().minusDays(days));
101         });
102     }
103 
104     public List<?> getSearchLogList(final SearchLogPager pager) {
105         final EsPagingResultBean<?> list;
106         if (SearchLogPager.LOG_TYPE_USERINFO.equalsIgnoreCase(pager.logType)) {
107             list = (EsPagingResultBean<?>) userInfoBhv.selectPage(cb -> {
108                 cb.paging(pager.getPageSize(), pager.getCurrentPageNumber());
109                 cb.query().addOrderBy_UpdatedAt_Desc();
110                 createUserInfoCondition(pager, cb);
111             });
112         } else if (SearchLogPager.LOG_TYPE_CLICK.equalsIgnoreCase(pager.logType)) {
113             list = (EsPagingResultBean<?>) clickLogBhv.selectPage(cb -> {
114                 cb.paging(pager.getPageSize(), pager.getCurrentPageNumber());
115                 cb.query().addOrderBy_RequestedAt_Desc();
116                 createClickLogCondition(pager, cb);
117             });
118         } else if (SearchLogPager.LOG_TYPE_CLICK_COUNT.equalsIgnoreCase(pager.logType)) {
119             list = (EsPagingResultBean<?>) clickLogBhv.selectPage(cb -> {
120                 cb.fetchFirst(0);
121                 createClickLogCondition(pager, cb);
122                 cb.aggregation().setUrl_Terms(SearchLogPager.LOG_TYPE_CLICK_COUNT, op -> {
123                     op.size(pager.getPageSize());
124                     if (fessConfig.getSearchlogAggShardSizeAsInteger() >= 0) {
125                         op.shardSize(fessConfig.getSearchlogAggShardSizeAsInteger());
126                     }
127                 }, null);
128             });
129             final Terms agg = list.getAggregations().get(SearchLogPager.LOG_TYPE_CLICK_COUNT);
130             final List<? extends Terms.Bucket> buckets = agg.getBuckets();
131             updatePagerByAgg(pager, buckets.size());
132             return buckets.stream().map(e -> {
133                 final Map<String, Object> map = new HashMap<>();
134                 map.put(ID, Base64.getUrlEncoder().encodeToString(e.getKeyAsString().getBytes(StandardCharsets.UTF_8)));
135                 map.put(KEY, e.getKeyAsString());
136                 map.put(COUNT, e.getDocCount());
137                 return map;
138             }).collect(Collectors.toList());
139         } else if (SearchLogPager.LOG_TYPE_FAVORITE.equalsIgnoreCase(pager.logType)) {
140             list = (EsPagingResultBean<?>) favoriteLogBhv.selectPage(cb -> {
141                 cb.paging(pager.getPageSize(), pager.getCurrentPageNumber());
142                 cb.query().addOrderBy_CreatedAt_Desc();
143                 createFavoriteLogCondition(pager, cb);
144             });
145         } else if (SearchLogPager.LOG_TYPE_FAVORITE_COUNT.equalsIgnoreCase(pager.logType)) {
146             list = (EsPagingResultBean<?>) favoriteLogBhv.selectPage(cb -> {
147                 cb.fetchFirst(0);
148                 createFavoriteLogCondition(pager, cb);
149                 cb.aggregation().setUrl_Terms(SearchLogPager.LOG_TYPE_FAVORITE_COUNT, op -> {
150                     op.size(pager.getPageSize());
151                     if (fessConfig.getSearchlogAggShardSizeAsInteger() >= 0) {
152                         op.shardSize(fessConfig.getSearchlogAggShardSizeAsInteger());
153                     }
154                 }, null);
155             });
156             final Terms agg = list.getAggregations().get(SearchLogPager.LOG_TYPE_FAVORITE_COUNT);
157             final List<? extends Terms.Bucket> buckets = agg.getBuckets();
158             updatePagerByAgg(pager, buckets.size());
159             return buckets.stream().map(e -> {
160                 final Map<String, Object> map = new HashMap<>();
161                 map.put(ID, Base64.getUrlEncoder().encodeToString(e.getKeyAsString().getBytes(StandardCharsets.UTF_8)));
162                 map.put(KEY, e.getKeyAsString());
163                 map.put(COUNT, e.getDocCount());
164                 return map;
165             }).collect(Collectors.toList());
166         } else if (SearchLogPager.LOG_TYPE_SEARCH_COUNT_HOUR.equalsIgnoreCase(pager.logType)) {
167             list = (EsPagingResultBean<?>) searchLogBhv.selectPage(cb -> {
168                 cb.fetchFirst(0);
169                 createSearchLogCondition(pager, cb);
170                 cb.aggregation().setRequestedAt_DateHistogram(SearchLogPager.LOG_TYPE_SEARCH_COUNT_HOUR, op -> {
171                     op.calendarInterval(DateHistogramInterval.HOUR);
172                     op.minDocCount(0);
173                     op.order(BucketOrder.key(true));
174                 }, null);
175             });
176             final Histogram agg = list.getAggregations().get(SearchLogPager.LOG_TYPE_SEARCH_COUNT_HOUR);
177             final List<? extends Histogram.Bucket> buckets = agg.getBuckets();
178             updatePagerByAgg(pager, buckets.size());
179             return buckets.stream().map(e -> {
180                 final Map<String, Object> map = new HashMap<>();
181                 map.put(ID, Base64.getUrlEncoder().encodeToString(e.getKeyAsString().getBytes(StandardCharsets.UTF_8)));
182                 map.put(KEY, e.getKeyAsString());
183                 map.put(COUNT, e.getDocCount());
184                 return map;
185             }).collect(Collectors.toList());
186         } else if (SearchLogPager.LOG_TYPE_SEARCH_COUNT_DAY.equalsIgnoreCase(pager.logType)) {
187             list = (EsPagingResultBean<?>) searchLogBhv.selectPage(cb -> {
188                 cb.fetchFirst(0);
189                 createSearchLogCondition(pager, cb);
190                 cb.aggregation().setRequestedAt_DateHistogram(SearchLogPager.LOG_TYPE_SEARCH_COUNT_DAY, op -> {
191                     op.calendarInterval(DateHistogramInterval.DAY);
192                     op.minDocCount(0);
193                     op.order(BucketOrder.key(true));
194                 }, null);
195             });
196             final Histogram agg = list.getAggregations().get(SearchLogPager.LOG_TYPE_SEARCH_COUNT_DAY);
197             final List<? extends Histogram.Bucket> buckets = agg.getBuckets();
198             updatePagerByAgg(pager, buckets.size());
199             return buckets.stream().map(e -> {
200                 final Map<String, Object> map = new HashMap<>();
201                 map.put(ID, Base64.getUrlEncoder().encodeToString(e.getKeyAsString().getBytes(StandardCharsets.UTF_8)));
202                 map.put(KEY, e.getKeyAsString());
203                 map.put(COUNT, e.getDocCount());
204                 return map;
205             }).collect(Collectors.toList());
206         } else if (SearchLogPager.LOG_TYPE_SEARCH_USER_HOUR.equalsIgnoreCase(pager.logType)) {
207             list = (EsPagingResultBean<?>) searchLogBhv.selectPage(cb -> {
208                 cb.fetchFirst(0);
209                 createSearchLogCondition(pager, cb);
210                 cb.aggregation().setRequestedAt_DateHistogram(SearchLogPager.LOG_TYPE_SEARCH_USER_HOUR, op -> {
211                     op.calendarInterval(DateHistogramInterval.HOUR);
212                     op.subAggregation(AggregationBuilders.cardinality(USER_INFO_ID).field(USER_INFO_ID));
213                     op.minDocCount(0);
214                     op.order(BucketOrder.key(true));
215                 }, null);
216             });
217             final Histogram agg = list.getAggregations().get(SearchLogPager.LOG_TYPE_SEARCH_USER_HOUR);
218             final List<? extends Histogram.Bucket> buckets = agg.getBuckets();
219             updatePagerByAgg(pager, buckets.size());
220             return buckets.stream().map(e -> {
221                 final Map<String, Object> map = new HashMap<>();
222                 map.put(ID, Base64.getUrlEncoder().encodeToString(e.getKeyAsString().getBytes(StandardCharsets.UTF_8)));
223                 map.put(KEY, e.getKeyAsString());
224                 final Cardinality value = e.getAggregations().get(USER_INFO_ID);
225                 map.put(COUNT, value.getValue());
226                 return map;
227             }).collect(Collectors.toList());
228         } else if (SearchLogPager.LOG_TYPE_SEARCH_USER_DAY.equalsIgnoreCase(pager.logType)) {
229             list = (EsPagingResultBean<?>) searchLogBhv.selectPage(cb -> {
230                 cb.fetchFirst(0);
231                 createSearchLogCondition(pager, cb);
232                 cb.aggregation().setRequestedAt_DateHistogram(SearchLogPager.LOG_TYPE_SEARCH_USER_DAY, op -> {
233                     op.calendarInterval(DateHistogramInterval.DAY);
234                     op.subAggregation(AggregationBuilders.cardinality(USER_INFO_ID).field(USER_INFO_ID));
235                     op.minDocCount(0);
236                     op.order(BucketOrder.key(true));
237                 }, null);
238             });
239             final Histogram agg = list.getAggregations().get(SearchLogPager.LOG_TYPE_SEARCH_USER_DAY);
240             final List<? extends Histogram.Bucket> buckets = agg.getBuckets();
241             updatePagerByAgg(pager, buckets.size());
242             return buckets.stream().map(e -> {
243                 final Map<String, Object> map = new HashMap<>();
244                 map.put(ID, Base64.getUrlEncoder().encodeToString(e.getKeyAsString().getBytes(StandardCharsets.UTF_8)));
245                 map.put(KEY, e.getKeyAsString());
246                 final Cardinality value = e.getAggregations().get(USER_INFO_ID);
247                 map.put(COUNT, value.getValue());
248                 return map;
249             }).collect(Collectors.toList());
250         } else if (SearchLogPager.LOG_TYPE_SEARCH_REQTIMEAVG_HOUR.equalsIgnoreCase(pager.logType)) {
251             list = (EsPagingResultBean<?>) searchLogBhv.selectPage(cb -> {
252                 cb.fetchFirst(0);
253                 createSearchLogCondition(pager, cb);
254                 cb.aggregation().setRequestedAt_DateHistogram(SearchLogPager.LOG_TYPE_SEARCH_REQTIMEAVG_HOUR, op -> {
255                     op.calendarInterval(DateHistogramInterval.HOUR);
256                     op.subAggregation(AggregationBuilders.avg(QUERY_TIME).field(QUERY_TIME));
257                     op.minDocCount(0);
258                     op.order(BucketOrder.key(true));
259                 }, null);
260             });
261             final Histogram agg = list.getAggregations().get(SearchLogPager.LOG_TYPE_SEARCH_REQTIMEAVG_HOUR);
262             final List<? extends Histogram.Bucket> buckets = agg.getBuckets();
263             updatePagerByAgg(pager, buckets.size());
264             return buckets.stream().map(e -> {
265                 final Map<String, Object> map = new HashMap<>();
266                 map.put(ID, Base64.getUrlEncoder().encodeToString(e.getKeyAsString().getBytes(StandardCharsets.UTF_8)));
267                 map.put(KEY, e.getKeyAsString());
268                 final Avg value = e.getAggregations().get(QUERY_TIME);
269                 map.put(COUNT, value.getValueAsString());
270                 return map;
271             }).collect(Collectors.toList());
272         } else if (SearchLogPager.LOG_TYPE_SEARCH_REQTIMEAVG_DAY.equalsIgnoreCase(pager.logType)) {
273             list = (EsPagingResultBean<?>) searchLogBhv.selectPage(cb -> {
274                 cb.fetchFirst(0);
275                 createSearchLogCondition(pager, cb);
276                 cb.aggregation().setRequestedAt_DateHistogram(SearchLogPager.LOG_TYPE_SEARCH_REQTIMEAVG_DAY, op -> {
277                     op.calendarInterval(DateHistogramInterval.DAY);
278                     op.subAggregation(AggregationBuilders.avg(QUERY_TIME).field(QUERY_TIME));
279                     op.minDocCount(0);
280                     op.order(BucketOrder.key(true));
281                 }, null);
282             });
283             final Histogram agg = list.getAggregations().get(SearchLogPager.LOG_TYPE_SEARCH_REQTIMEAVG_DAY);
284             final List<? extends Histogram.Bucket> buckets = agg.getBuckets();
285             updatePagerByAgg(pager, buckets.size());
286             return buckets.stream().map(e -> {
287                 final Map<String, Object> map = new HashMap<>();
288                 map.put(ID, Base64.getUrlEncoder().encodeToString(e.getKeyAsString().getBytes(StandardCharsets.UTF_8)));
289                 map.put(KEY, e.getKeyAsString());
290                 final Avg value = e.getAggregations().get(QUERY_TIME);
291                 map.put(COUNT, value.getValueAsString());
292                 return map;
293             }).collect(Collectors.toList());
294         } else if (SearchLogPager.LOG_TYPE_SEARCH_KEYWORD.equalsIgnoreCase(pager.logType)) {
295             list = (EsPagingResultBean<?>) searchLogBhv.selectPage(cb -> {
296                 cb.fetchFirst(0);
297                 createSearchLogCondition(pager, cb);
298                 cb.aggregation().setSearchWord_Terms(SearchLogPager.LOG_TYPE_SEARCH_KEYWORD, op -> {
299                     op.size(pager.getPageSize());
300                     if (fessConfig.getSearchlogAggShardSizeAsInteger() >= 0) {
301                         op.shardSize(fessConfig.getSearchlogAggShardSizeAsInteger());
302                     }
303                 }, null);
304             });
305             final Terms agg = list.getAggregations().get(SearchLogPager.LOG_TYPE_SEARCH_KEYWORD);
306             final List<? extends Terms.Bucket> buckets = agg.getBuckets();
307             updatePagerByAgg(pager, buckets.size());
308             return buckets.stream().map(e -> {
309                 final Map<String, Object> map = new HashMap<>();
310                 map.put(ID, Base64.getUrlEncoder().encodeToString(e.getKeyAsString().getBytes(StandardCharsets.UTF_8)));
311                 map.put(KEY, e.getKeyAsString());
312                 map.put(COUNT, e.getDocCount());
313                 return map;
314             }).collect(Collectors.toList());
315         } else if (SearchLogPager.LOG_TYPE_SEARCH_ZEROHIT.equalsIgnoreCase(pager.logType)) {
316             list = (EsPagingResultBean<?>) searchLogBhv.selectPage(cb -> {
317                 cb.fetchFirst(0);
318                 createSearchLogCondition(pager, cb);
319                 cb.query().setHitCount_Equal(0L);
320                 cb.aggregation().setSearchWord_Terms(SearchLogPager.LOG_TYPE_SEARCH_ZEROHIT, op -> {
321                     op.size(pager.getPageSize());
322                     if (fessConfig.getSearchlogAggShardSizeAsInteger() >= 0) {
323                         op.shardSize(fessConfig.getSearchlogAggShardSizeAsInteger());
324                     }
325                 }, null);
326             });
327             final Terms agg = list.getAggregations().get(SearchLogPager.LOG_TYPE_SEARCH_ZEROHIT);
328             final List<? extends Terms.Bucket> buckets = agg.getBuckets();
329             updatePagerByAgg(pager, buckets.size());
330             return buckets.stream().map(e -> {
331                 final Map<String, Object> map = new HashMap<>();
332                 map.put(ID, Base64.getUrlEncoder().encodeToString(e.getKeyAsString().getBytes(StandardCharsets.UTF_8)));
333                 map.put(KEY, e.getKeyAsString());
334                 map.put(COUNT, e.getDocCount());
335                 return map;
336             }).collect(Collectors.toList());
337             //        } else if (SearchLogPager.LOG_TYPE_SEARCH_ZEROCLICK.equalsIgnoreCase(pager.logType)) {
338             //            list = (EsPagingResultBean<?>) searchLogBhv.selectPage(cb -> {
339             //                cb.fetchFirst(0);
340             //                createSearchLogCondition(pager, cb);
341             //                // TODO 0 clicked
342             //                });
343         } else {
344             list = (EsPagingResultBean<?>) searchLogBhv.selectPage(cb -> {
345                 cb.paging(pager.getPageSize(), pager.getCurrentPageNumber());
346                 cb.query().addOrderBy_RequestedAt_Desc();
347                 createSearchLogCondition(pager, cb);
348             });
349         }
350 
351         // update pager
352         BeanUtil.copyBeanToBean(list, pager, option -> option.include(Constants.PAGER_CONVERSION_RULE));
353         pager.setPageNumberList(list.pageRange(op -> {
354             op.rangeSize(fessConfig.getPagingPageRangeSizeAsInteger());
355         }).createPageNumberList());
356 
357         return list;
358     }
359 
360     private void updatePagerByAgg(final SearchLogPager pager, final int size) {
361         pager.setAllPageCount(1);
362         pager.setAllRecordCount(size);
363         pager.setCurrentPageNumber(1);
364         pager.setExistNextPage(false);
365         pager.setExistPrePage(false);
366         pager.setPageSize(pager.getPageSize());
367     }
368 
369     private void createSearchLogCondition(final SearchLogPager pager, final SearchLogCB cb) {
370         if (StringUtil.isNotBlank(pager.queryId)) {
371             cb.query().setQueryId_Term(pager.queryId);
372         }
373         if (StringUtil.isNotBlank(pager.userSessionId)) {
374             cb.query().setUserSessionId_Term(pager.userSessionId);
375         }
376         if (StringUtil.isNotBlank(pager.accessType)) {
377             cb.query().setAccessType_Term(pager.accessType);
378         }
379         if (StringUtil.isNotBlank(pager.requestedTimeRange)) {
380             final String[] values = pager.requestedTimeRange.split(" - ");
381             final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM);
382             try {
383                 if (values.length > 0) {
384                     cb.query().setRequestedAt_GreaterEqual(parseDateTime(values[0], formatter));
385                 }
386                 if (values.length > 1) {
387                     cb.query().setRequestedAt_LessEqual(LocalDateTime.parse(values[1], formatter));
388                 }
389             } catch (final Exception e) {
390                 if (logger.isDebugEnabled()) {
391                     logger.debug("Failed to parse {}", pager.requestedTimeRange, e);
392                 }
393             }
394         }
395     }
396 
397     private void createFavoriteLogCondition(final SearchLogPager pager, final FavoriteLogCB cb) {
398         if (StringUtil.isNotBlank(pager.queryId)) {
399             cb.query().setQueryId_Term(pager.queryId);
400         }
401         if (StringUtil.isNotBlank(pager.userSessionId)) {
402             cb.query().setUserInfoId_Term(pager.userSessionId);
403         }
404         if (StringUtil.isNotBlank(pager.requestedTimeRange)) {
405             final String[] values = pager.requestedTimeRange.split(" - ");
406             final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM);
407             try {
408                 if (values.length > 0) {
409                     cb.query().setCreatedAt_GreaterEqual(LocalDateTime.parse(values[0], formatter));
410                 }
411                 if (values.length > 1) {
412                     cb.query().setCreatedAt_LessEqual(parseDateTime(values[1], formatter));
413                 }
414             } catch (final Exception e) {
415                 if (logger.isDebugEnabled()) {
416                     logger.debug("Failed to parse {}", pager.requestedTimeRange, e);
417                 }
418             }
419         }
420     }
421 
422     private void createUserInfoCondition(final SearchLogPager pager, final UserInfoCB cb) {
423         if (StringUtil.isNotBlank(pager.userSessionId)) {
424             cb.query().setId_Equal(pager.userSessionId);
425         }
426         if (StringUtil.isNotBlank(pager.requestedTimeRange)) {
427             final String[] values = pager.requestedTimeRange.split(" - ");
428             final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM);
429             try {
430                 if (values.length > 0) {
431                     cb.query().setUpdatedAt_GreaterEqual(LocalDateTime.parse(values[0], formatter));
432                 }
433                 if (values.length > 1) {
434                     cb.query().setUpdatedAt_LessEqual(LocalDateTime.parse(values[1], formatter));
435                 }
436             } catch (final Exception e) {
437                 if (logger.isDebugEnabled()) {
438                     logger.debug("Failed to parse {}", pager.requestedTimeRange, e);
439                 }
440             }
441         }
442     }
443 
444     private void createClickLogCondition(final SearchLogPager pager, final ClickLogCB cb) {
445         if (StringUtil.isNotBlank(pager.queryId)) {
446             cb.query().setQueryId_Term(pager.queryId);
447         }
448         if (StringUtil.isNotBlank(pager.userSessionId)) {
449             cb.query().setUserSessionId_Term(pager.userSessionId);
450         }
451         if (StringUtil.isNotBlank(pager.requestedTimeRange)) {
452             final String[] values = pager.requestedTimeRange.split(" - ");
453             final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM);
454             try {
455                 if (values.length > 0) {
456                     cb.query().setRequestedAt_GreaterEqual(LocalDateTime.parse(values[0], formatter));
457                 }
458                 if (values.length > 1) {
459                     cb.query().setRequestedAt_LessEqual(LocalDateTime.parse(values[1], formatter));
460                 }
461             } catch (final Exception e) {
462                 if (logger.isDebugEnabled()) {
463                     logger.debug("Failed to parse {}", pager.requestedTimeRange, e);
464                 }
465             }
466         }
467     }
468 
469     protected LocalDateTime parseDateTime(final String value, final DateTimeFormatter formatter) {
470         return LocalDateTime.parse(value, formatter).atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
471     }
472 
473     public OptionalEntity<?> getSearchLog(final String logType, final String id) {
474         if (SearchLogPager.LOG_TYPE_CLICK.equalsIgnoreCase(logType)) {
475             return clickLogBhv.selectByPK(id);
476         }
477         if (SearchLogPager.LOG_TYPE_FAVORITE.equalsIgnoreCase(logType)) {
478             return favoriteLogBhv.selectByPK(id);
479         }
480         if (SearchLogPager.LOG_TYPE_USERINFO.equalsIgnoreCase(logType)) {
481             return userInfoBhv.selectByPK(id);
482         } else {
483             return searchLogBhv.selectByPK(id);
484         }
485     }
486 
487     public Map<String, String> getSearchLogMap(final String logType, final String id) {
488         if (SearchLogPager.LOG_TYPE_USERINFO.equalsIgnoreCase(logType)) {
489             return userInfoBhv.selectByPK(id).map(e -> {
490                 final Map<String, String> params = new LinkedHashMap<>();
491                 params.put("User Info ID", e.getId());
492                 params.put("Created Time", FessFunctions.formatDate(e.getCreatedAt()));
493                 params.put("Updated Time", FessFunctions.formatDate(e.getUpdatedAt()));
494                 return params;
495             }).get();
496         }
497         if (SearchLogPager.LOG_TYPE_CLICK.equalsIgnoreCase(logType)) {
498             return clickLogBhv.selectByPK(id).map(e -> {
499                 final Map<String, String> params = new LinkedHashMap<>();
500                 params.put("ID", e.getId());
501                 params.put("Query ID", e.getQueryId());
502                 params.put("Doc ID", e.getDocId());
503                 params.put("User Session ID", e.getUserSessionId());
504                 params.put("URL", e.getUrl());
505                 params.put("URL ID", e.getUrlId());
506                 params.put("Order", toNumberString(e.getOrder()));
507                 params.put("Query Requested Time", FessFunctions.formatDate(e.getQueryRequestedAt()));
508                 params.put("Requested Time", FessFunctions.formatDate(e.getRequestedAt()));
509                 return params;
510             }).get();
511         }
512         if (SearchLogPager.LOG_TYPE_FAVORITE.equalsIgnoreCase(logType)) {
513             return favoriteLogBhv.selectByPK(id).map(e -> {
514                 final Map<String, String> params = new LinkedHashMap<>();
515                 params.put("ID", e.getId());
516                 params.put("Query ID", e.getQueryId());
517                 params.put("Doc ID", e.getDocId());
518                 params.put("User Info ID", e.getUserInfoId());
519                 params.put("URL", e.getUrl());
520                 params.put("Created Time", FessFunctions.formatDate(e.getCreatedAt()));
521                 params.put("Requested Time", FessFunctions.formatDate(e.getRequestedAt()));
522                 return params;
523             }).get();
524         } else {
525             return searchLogBhv.selectByPK(id).map(e -> {
526                 final Map<String, String> params = new LinkedHashMap<>();
527                 params.put("ID", e.getId());
528                 params.put("Query ID", e.getQueryId());
529                 params.put("User Info ID", e.getUserInfoId());
530                 params.put("User Session ID", e.getUserSessionId());
531                 params.put("Access Type", e.getAccessType());
532                 params.put("Search Word", e.getSearchWord());
533                 params.put("Requested Time", FessFunctions.formatDate(e.getRequestedAt()));
534                 params.put("Query Time", toNumberString(e.getQueryTime()));
535                 params.put("Response Time", toNumberString(e.getResponseTime()));
536                 params.put("Hit Count", toNumberString(e.getHitCount()));
537                 params.put("Offset", toNumberString(e.getQueryOffset()));
538                 params.put("Page Size", toNumberString(e.getQueryPageSize()));
539                 params.put("Client IP", e.getClientIp());
540                 params.put("Referer", e.getReferer());
541                 params.put("Languages", e.getLanguages());
542                 params.put("Virtual Host", e.getVirtualHost());
543                 params.put("Roles", e.getRoles() != null ? String.join(" ", e.getRoles()) : StringUtil.EMPTY);
544                 params.put("User Agent", e.getUserAgent());
545                 e.getSearchFieldLogList().stream().forEach(p -> {
546                     params.put(p.getFirst(), p.getSecond());
547                 });
548                 return params;
549             }).get();
550         }
551     }
552 
553     private String toNumberString(final Number value) {
554         return value != null ? value.toString() : StringUtil.EMPTY;
555     }
556 
557     public void deleteSearchLog(final Object e) {
558         if (e instanceof ClickLog) {
559             clickLogBhv.delete((ClickLog) e);
560         } else if (e instanceof FavoriteLog) {
561             favoriteLogBhv.delete((FavoriteLog) e);
562         } else if (e instanceof UserInfo) {
563             userInfoBhv.delete((UserInfo) e);
564         } else if (e instanceof SearchLog) {
565             searchLogBhv.delete((SearchLog) e);
566         } else {
567             throw new FessSystemException("Unknown log entity: " + e);
568         }
569     }
570 }