1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 org.apache.logging.log4j.LogManager;
26 import org.apache.logging.log4j.Logger;
27 import org.codelibs.core.lang.StringUtil;
28 import org.codelibs.core.misc.Tuple3;
29 import org.codelibs.fess.Constants;
30 import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
31 import org.codelibs.fess.mylasta.direction.FessConfig;
32 import org.codelibs.fess.opensearch.client.SearchEngineClient;
33 import org.codelibs.fess.opensearch.client.SearchEngineClient.SearchConditionBuilder;
34 import org.codelibs.fess.opensearch.config.exbhv.KeyMatchBhv;
35 import org.codelibs.fess.opensearch.config.exentity.KeyMatch;
36 import org.codelibs.fess.util.ComponentUtil;
37 import org.codelibs.fess.util.DocumentUtil;
38 import org.opensearch.index.query.BoolQueryBuilder;
39 import org.opensearch.index.query.QueryBuilder;
40 import org.opensearch.index.query.QueryBuilders;
41 import org.opensearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder;
42 import org.opensearch.index.query.functionscore.ScoreFunctionBuilder;
43 import org.opensearch.index.query.functionscore.ScoreFunctionBuilders;
44
45 import jakarta.annotation.PostConstruct;
46
47
48
49
50
51 public class KeyMatchHelper extends AbstractConfigHelper {
52 private static final Logger logger = LogManager.getLogger(KeyMatchHelper.class);
53
54
55
56
57 public KeyMatchHelper() {
58 super();
59 }
60
61
62
63
64
65 protected volatile Map<String, Map<String, List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>>>> keyMatchQueryMap =
66 Collections.emptyMap();
67
68
69
70
71
72 @PostConstruct
73 public void init() {
74 if (logger.isDebugEnabled()) {
75 logger.debug("Initializing {}", this.getClass().getSimpleName());
76 }
77 load();
78 }
79
80
81
82
83
84
85 public List<KeyMatch> getAvailableKeyMatchList() {
86 return ComponentUtil.getComponent(KeyMatchBhv.class).selectList(cb -> {
87 cb.query().matchAll();
88 cb.fetchFirst(ComponentUtil.getFessConfig().getPageKeymatchMaxFetchSizeAsInteger());
89 });
90 }
91
92
93
94
95
96
97 @Override
98 public int load() {
99 final FessConfig fessConfig = ComponentUtil.getFessConfig();
100 final Map<String, Map<String, List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>>>> keyMatchQueryMap = new HashMap<>();
101 getAvailableKeyMatchList().stream().forEach(keyMatch -> {
102 try {
103 final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
104 if (logger.isDebugEnabled()) {
105 logger.debug("Loading KeyMatch Query: {}, Size: {}", keyMatch.getQuery(), keyMatch.getMaxSize());
106 }
107 getDocumentList(keyMatch).stream().map(doc -> {
108 if (logger.isDebugEnabled()) {
109 logger.debug("Loaded KeyMatch doc: {}", doc);
110 }
111 return DocumentUtil.getValue(doc, fessConfig.getIndexFieldDocId(), String.class);
112 }).forEach(docId -> {
113 boolQuery.should(QueryBuilders.termQuery(fessConfig.getIndexFieldDocId(), docId));
114 });
115
116 if (boolQuery.hasClauses()) {
117 if (logger.isDebugEnabled()) {
118 logger.debug("Loaded KeyMatch Boost Query: {}", boolQuery);
119 }
120 String virtualHost = keyMatch.getVirtualHost();
121 if (StringUtil.isBlank(virtualHost)) {
122 virtualHost = StringUtil.EMPTY;
123 }
124 Map<String, List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>>> queryMap = keyMatchQueryMap.get(virtualHost);
125 if (queryMap == null) {
126 queryMap = new HashMap<>();
127 keyMatchQueryMap.put(virtualHost, queryMap);
128 }
129 final String termKey = toLowerCase(keyMatch.getTerm());
130 List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>> boostList = queryMap.get(termKey);
131 if (boostList == null) {
132 boostList = new ArrayList<>();
133 queryMap.put(termKey, boostList);
134 }
135 boostList.add(
136 new Tuple3<>(keyMatch.getId(), boolQuery, ScoreFunctionBuilders.weightFactorFunction(keyMatch.getBoost())));
137 } else if (logger.isDebugEnabled()) {
138 logger.debug("No KeyMatch boost docs");
139 }
140
141 waitForNext();
142 } catch (final Exception e) {
143 logger.warn("Failed to load KeyMatch: id={}, term={}", keyMatch.getId(), keyMatch.getTerm(), e);
144 }
145 });
146 this.keyMatchQueryMap = keyMatchQueryMap;
147 return keyMatchQueryMap.size();
148 }
149
150
151
152
153
154
155
156 protected List<Map<String, Object>> getDocumentList(final KeyMatch keyMatch) {
157 final SearchEngineClient searchEngineClient = ComponentUtil.getSearchEngineClient();
158 final FessConfig fessConfig = ComponentUtil.getFessConfig();
159 return searchEngineClient.getDocumentList(fessConfig.getIndexDocumentSearchIndex(),
160 searchRequestBuilder -> SearchConditionBuilder
161 .builder(searchRequestBuilder.setPreference(Constants.SEARCH_PREFERENCE_LOCAL))
162 .searchRequestType(SearchRequestType.ADMIN_SEARCH)
163 .size(keyMatch.getMaxSize())
164 .query(keyMatch.getQuery())
165 .responseFields(new String[] { fessConfig.getIndexFieldDocId() })
166 .build());
167 }
168
169
170
171
172
173
174
175 protected Map<String, List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>>> getQueryMap(final String key) {
176 final Map<String, List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>>> map = keyMatchQueryMap.get(key);
177 if (map != null) {
178 return map;
179 }
180 return Collections.emptyMap();
181 }
182
183
184
185
186
187
188
189 public void buildQuery(final List<String> keywordList, final List<FilterFunctionBuilder> list) {
190 final String key = ComponentUtil.getVirtualHostHelper().getVirtualHostKey();
191 keywordList.stream().forEach(keyword -> {
192 final List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>> boostList = getQueryMap(key).get(toLowerCase(keyword));
193 if (boostList != null) {
194 boostList.forEach(pair -> list.add(new FilterFunctionBuilder(pair.getValue2(), pair.getValue3())));
195 }
196 });
197 }
198
199
200
201
202
203
204
205 public List<Map<String, Object>> getBoostedDocumentList(final KeyMatch keyMatch) {
206 final SearchEngineClient searchEngineClient = ComponentUtil.getSearchEngineClient();
207 String virtualHost = keyMatch.getVirtualHost();
208 if (StringUtil.isBlank(virtualHost)) {
209 virtualHost = StringUtil.EMPTY;
210 }
211 final List<Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>>> boostList =
212 getQueryMap(virtualHost).get(toLowerCase(keyMatch.getTerm()));
213 if (boostList == null) {
214 return Collections.emptyList();
215 }
216 for (final Tuple3<String, QueryBuilder, ScoreFunctionBuilder<?>> pair : boostList) {
217 if (!keyMatch.getId().equals(pair.getValue1())) {
218 continue;
219 }
220 final FessConfig fessConfig = ComponentUtil.getFessConfig();
221 return searchEngineClient.getDocumentList(fessConfig.getIndexDocumentSearchIndex(), searchRequestBuilder -> {
222 searchRequestBuilder.setPreference(Constants.SEARCH_PREFERENCE_LOCAL)
223 .setQuery(pair.getValue2())
224 .setSize(keyMatch.getMaxSize());
225 return true;
226 });
227 }
228 return Collections.emptyList();
229 }
230
231
232
233
234
235
236
237 private String toLowerCase(final String term) {
238 return term != null ? term.toLowerCase(Locale.ROOT) : term;
239 }
240
241 }