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.helper;
17  
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Locale;
23  import java.util.Map;
24  
25  import javax.annotation.PostConstruct;
26  
27  import org.apache.logging.log4j.LogManager;
28  import org.apache.logging.log4j.Logger;
29  import org.codelibs.core.lang.StringUtil;
30  import org.codelibs.core.misc.Tuple3;
31  import org.codelibs.fesen.index.query.BoolQueryBuilder;
32  import org.codelibs.fesen.index.query.QueryBuilder;
33  import org.codelibs.fesen.index.query.QueryBuilders;
34  import org.codelibs.fesen.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder;
35  import org.codelibs.fesen.index.query.functionscore.ScoreFunctionBuilder;
36  import org.codelibs.fesen.index.query.functionscore.ScoreFunctionBuilders;
37  import org.codelibs.fess.Constants;
38  import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
39  import org.codelibs.fess.es.client.SearchEngineClient;
40  import org.codelibs.fess.es.client.SearchEngineClient.SearchConditionBuilder;
41  import org.codelibs.fess.es.config.exbhv.KeyMatchBhv;
42  import org.codelibs.fess.es.config.exentity.KeyMatch;
43  import org.codelibs.fess.mylasta.direction.FessConfig;
44  import org.codelibs.fess.util.ComponentUtil;
45  import org.codelibs.fess.util.DocumentUtil;
46  
47  public class KeyMatchHelper extends AbstractConfigHelper {
48      private static final Logger logger = LogManager.getLogger(KeyMatchHelper.class);
49  
50      protected volatile Map<String, Map<String, List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>>>> keyMatchQueryMap =
51              Collections.emptyMap();
52  
53      @PostConstruct
54      public void init() {
55          if (logger.isDebugEnabled()) {
56              logger.debug("Initialize {}", this.getClass().getSimpleName());
57          }
58          load();
59      }
60  
61      public List<KeyMatch> getAvailableKeyMatchList() {
62          return ComponentUtil.getComponent(KeyMatchBhv.class).selectList(cb -> {
63              cb.query().matchAll();
64              cb.fetchFirst(ComponentUtil.getFessConfig().getPageKeymatchMaxFetchSizeAsInteger());
65          });
66      }
67  
68      @Override
69      public int load() {
70          final FessConfig fessConfig = ComponentUtil.getFessConfig();
71          final Map<String, Map<String, List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>>>> keyMatchQueryMap = new HashMap<>();
72          getAvailableKeyMatchList().stream().forEach(keyMatch -> {
73              try {
74                  final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
75                  if (logger.isDebugEnabled()) {
76                      logger.debug("Loading KeyMatch Query: {}, Size: {}", keyMatch.getQuery(), keyMatch.getMaxSize());
77                  }
78                  getDocumentList(keyMatch).stream().map(doc -> {
79                      if (logger.isDebugEnabled()) {
80                          logger.debug("Loaded KeyMatch doc: {}", doc);
81                      }
82                      return DocumentUtil.getValue(doc, fessConfig.getIndexFieldDocId(), String.class);
83                  }).forEach(docId -> {
84                      boolQuery.should(QueryBuilders.termQuery(fessConfig.getIndexFieldDocId(), docId));
85                  });
86  
87                  if (boolQuery.hasClauses()) {
88                      if (logger.isDebugEnabled()) {
89                          logger.debug("Loaded KeyMatch Boost Query: {}", boolQuery);
90                      }
91                      String virtualHost = keyMatch.getVirtualHost();
92                      if (StringUtil.isBlank(virtualHost)) {
93                          virtualHost = StringUtil.EMPTY;
94                      }
95                      Map<String, List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>>> queryMap = keyMatchQueryMap.get(virtualHost);
96                      if (queryMap == null) {
97                          queryMap = new HashMap<>();
98                          keyMatchQueryMap.put(virtualHost, queryMap);
99                      }
100                     final String termKey = toLowerCase(keyMatch.getTerm());
101                     List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>> boostList = queryMap.get(termKey);
102                     if (boostList == null) {
103                         boostList = new ArrayList<>();
104                         queryMap.put(termKey, boostList);
105                     }
106                     boostList.add(
107                             new Tuple3<>(keyMatch.getId(), boolQuery, ScoreFunctionBuilders.weightFactorFunction(keyMatch.getBoost())));
108                 } else if (logger.isDebugEnabled()) {
109                     logger.debug("No KeyMatch boost docs");
110                 }
111 
112                 waitForNext();
113             } catch (final Exception e) {
114                 logger.warn("Cannot load {}", keyMatch, e);
115             }
116         });
117         this.keyMatchQueryMap = keyMatchQueryMap;
118         return keyMatchQueryMap.size();
119     }
120 
121     protected List<Map<String, Object>> getDocumentList(final KeyMatch keyMatch) {
122         final SearchEngineClient searchEngineClient = ComponentUtil.getSearchEngineClient();
123         final FessConfig fessConfig = ComponentUtil.getFessConfig();
124         return searchEngineClient.getDocumentList(fessConfig.getIndexDocumentSearchIndex(),
125                 searchRequestBuilder -> SearchConditionBuilder
126                         .builder(searchRequestBuilder.setPreference(Constants.SEARCH_PREFERENCE_LOCAL))
127                         .searchRequestType(SearchRequestType.ADMIN_SEARCH).size(keyMatch.getMaxSize()).query(keyMatch.getQuery())
128                         .responseFields(new String[] { fessConfig.getIndexFieldDocId() }).build());
129     }
130 
131     protected Map<String, List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>>> getQueryMap(final String key) {
132         final Map<String, List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>>> map = keyMatchQueryMap.get(key);
133         if (map != null) {
134             return map;
135         }
136         return Collections.emptyMap();
137     }
138 
139     public void buildQuery(final List<String> keywordList, final List<FilterFunctionBuilder> list) {
140         final String key = ComponentUtil.getVirtualHostHelper().getVirtualHostKey();
141         keywordList.stream().forEach(keyword -> {
142             final List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>> boostList = getQueryMap(key).get(toLowerCase(keyword));
143             if (boostList != null) {
144                 boostList.forEach(pair -> list.add(new FilterFunctionBuilder(pair.getValue2(), pair.getValue3())));
145             }
146         });
147     }
148 
149     public List<Map<String, Object>> getBoostedDocumentList(final KeyMatch keyMatch) {
150         final SearchEngineClient searchEngineClient = ComponentUtil.getSearchEngineClient();
151         String virtualHost = keyMatch.getVirtualHost();
152         if (StringUtil.isBlank(virtualHost)) {
153             virtualHost = StringUtil.EMPTY;
154         }
155         final List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>> boostList =
156                 getQueryMap(virtualHost).get(toLowerCase(keyMatch.getTerm()));
157         if (boostList == null) {
158             return Collections.emptyList();
159         }
160         for (final Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>> pair : boostList) {
161             if (!keyMatch.getId().equals(pair.getValue1())) {
162                 continue;
163             }
164             final FessConfig fessConfig = ComponentUtil.getFessConfig();
165             return searchEngineClient.getDocumentList(fessConfig.getIndexDocumentSearchIndex(), searchRequestBuilder -> {
166                 searchRequestBuilder.setPreference(Constants.SEARCH_PREFERENCE_LOCAL).setQuery(pair.getValue2())
167                         .setSize(keyMatch.getMaxSize());
168                 return true;
169             });
170         }
171         return Collections.emptyList();
172     }
173 
174     private String toLowerCase(final String term) {
175         return term != null ? term.toLowerCase(Locale.ROOT) : term;
176     }
177 
178 }