1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.app.service;
17
18 import java.text.NumberFormat;
19 import java.util.Enumeration;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Locale;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.concurrent.ExecutionException;
26 import java.util.function.Consumer;
27
28 import javax.annotation.Resource;
29 import javax.servlet.http.HttpServletRequest;
30
31 import org.codelibs.core.lang.StringUtil;
32 import org.codelibs.fess.Constants;
33 import org.codelibs.fess.entity.QueryContext;
34 import org.codelibs.fess.entity.SearchRenderData;
35 import org.codelibs.fess.entity.SearchRequestParams;
36 import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
37 import org.codelibs.fess.es.client.FessEsClient;
38 import org.codelibs.fess.es.client.FessEsClient.SearchConditionBuilder;
39 import org.codelibs.fess.es.client.FessEsClientException;
40 import org.codelibs.fess.helper.QueryHelper;
41 import org.codelibs.fess.helper.SystemHelper;
42 import org.codelibs.fess.mylasta.action.FessUserBean;
43 import org.codelibs.fess.mylasta.direction.FessConfig;
44 import org.codelibs.fess.util.ComponentUtil;
45 import org.codelibs.fess.util.QueryResponseList;
46 import org.codelibs.fess.util.QueryStringBuilder;
47 import org.dbflute.optional.OptionalEntity;
48 import org.dbflute.optional.OptionalThing;
49 import org.dbflute.util.DfTypeUtil;
50 import org.elasticsearch.ElasticsearchException;
51 import org.elasticsearch.action.DocWriteResponse.Result;
52 import org.elasticsearch.action.bulk.BulkRequestBuilder;
53 import org.elasticsearch.action.bulk.BulkResponse;
54 import org.elasticsearch.action.update.UpdateRequestBuilder;
55 import org.elasticsearch.action.update.UpdateResponse;
56 import org.elasticsearch.index.query.BoolQueryBuilder;
57 import org.elasticsearch.index.query.QueryBuilders;
58 import org.lastaflute.taglib.function.LaFunctions;
59 import org.lastaflute.web.util.LaRequestUtil;
60
61 public class SearchService {
62
63
64
65
66
67
68
69
70
71 @Resource
72 protected FessEsClient fessEsClient;
73
74 @Resource
75 protected SystemHelper systemHelper;
76
77 @Resource
78 protected FessConfig fessConfig;
79
80 @Resource
81 protected QueryHelper queryHelper;
82
83
84
85
86
87 public void search(final SearchRequestParams params, final SearchRenderData data, final OptionalThing<FessUserBean> userBean) {
88 final long requestedTime = systemHelper.getCurrentTimeAsLong();
89 final long startTime = System.currentTimeMillis();
90
91 LaRequestUtil.getOptionalRequest().ifPresent(request -> {
92 request.setAttribute(Constants.REQUEST_LANGUAGES, params.getLanguages());
93 request.setAttribute(Constants.REQUEST_QUERIES, params.getQuery());
94 });
95
96 final String query =
97 QueryStringBuilder.query(params.getQuery()).extraQueries(params.getExtraQueries()).fields(params.getFields()).build();
98
99 final int pageStart = params.getStartPosition();
100 final int pageSize = params.getPageSize();
101 final String sortField = params.getSort();
102 final List<Map<String, Object>> documentItems =
103 fessEsClient.search(
104 fessConfig.getIndexDocumentSearchIndex(),
105 fessConfig.getIndexDocumentType(),
106 searchRequestBuilder -> {
107 fessConfig.processSearchPreference(searchRequestBuilder, userBean);
108 return SearchConditionBuilder.builder(searchRequestBuilder)
109 .query(StringUtil.isBlank(sortField) ? query : query + " sort:" + sortField).offset(pageStart)
110 .size(pageSize).facetInfo(params.getFacetInfo()).geoInfo(params.getGeoInfo())
111 .similarDocHash(params.getSimilarDocHash()).responseFields(queryHelper.getResponseFields())
112 .searchRequestType(params.getType()).build();
113 }, (searchRequestBuilder, execTime, searchResponse) -> {
114 final QueryResponseList queryResponseList = ComponentUtil.getQueryResponseList();
115 queryResponseList.init(searchResponse, pageStart, pageSize);
116 return queryResponseList;
117 });
118 data.setDocumentItems(documentItems);
119
120
121 final QueryResponseList queryResponseList = (QueryResponseList) documentItems;
122 data.setFacetResponse(queryResponseList.getFacetResponse());
123
124 @SuppressWarnings("unchecked")
125 final Set<String> highlightQueries = (Set<String>) params.getAttribute(Constants.HIGHLIGHT_QUERIES);
126 if (highlightQueries != null) {
127 final StringBuilder buf = new StringBuilder(100);
128 highlightQueries.stream().forEach(q -> {
129 buf.append("&hq=").append(LaFunctions.u(q));
130 });
131 data.setAppendHighlightParams(buf.toString());
132 }
133
134 queryResponseList.setExecTime(System.currentTimeMillis() - startTime);
135 final NumberFormat nf = NumberFormat.getInstance(params.getLocale());
136 nf.setMaximumIntegerDigits(2);
137 nf.setMaximumFractionDigits(2);
138 String execTime;
139 try {
140 execTime = nf.format((double) queryResponseList.getExecTime() / 1000);
141 } catch (final Exception e) {
142 execTime = StringUtil.EMPTY;
143 }
144 data.setExecTime(execTime);
145
146 final String queryId = queryHelper.generateId();
147
148 data.setPageSize(queryResponseList.getPageSize());
149 data.setCurrentPageNumber(queryResponseList.getCurrentPageNumber());
150 data.setAllRecordCount(queryResponseList.getAllRecordCount());
151 data.setAllPageCount(queryResponseList.getAllPageCount());
152 data.setExistNextPage(queryResponseList.isExistNextPage());
153 data.setExistPrevPage(queryResponseList.isExistPrevPage());
154 data.setCurrentStartRecordNumber(queryResponseList.getCurrentStartRecordNumber());
155 data.setCurrentEndRecordNumber(queryResponseList.getCurrentEndRecordNumber());
156 data.setPageNumberList(queryResponseList.getPageNumberList());
157 data.setPartialResults(queryResponseList.isPartialResults());
158 data.setQueryTime(queryResponseList.getQueryTime());
159 data.setSearchQuery(query);
160 data.setRequestedTime(requestedTime);
161 data.setQueryId(queryId);
162
163
164 if (fessConfig.isSearchLog()) {
165 ComponentUtil.getSearchLogHelper().addSearchLog(params, DfTypeUtil.toLocalDateTime(requestedTime), queryId, query, pageStart,
166 pageSize, queryResponseList);
167 }
168
169
170 if (fessConfig.isUserFavorite()) {
171 ComponentUtil.getUserInfoHelper().storeQueryId(queryId, documentItems);
172 }
173
174 }
175
176 public int deleteByQuery(final HttpServletRequest request, final SearchRequestParams params) {
177
178 final String query =
179 QueryStringBuilder.query(params.getQuery()).extraQueries(params.getExtraQueries()).fields(params.getFields()).build();
180
181 final QueryContext queryContext = queryHelper.build(params.getType(), query, context -> {
182 context.skipRoleQuery();
183 });
184 return fessEsClient.deleteByQuery(fessConfig.getIndexDocumentUpdateIndex(), fessConfig.getIndexDocumentType(),
185 queryContext.getQueryBuilder());
186 }
187
188 public String[] getLanguages(final HttpServletRequest request, final SearchRequestParams params) {
189 if (params.getLanguages() != null) {
190 final Set<String> langSet = new HashSet<>();
191 for (final String lang : params.getLanguages()) {
192 if (StringUtil.isNotBlank(lang) && lang.length() < 1000) {
193 if (Constants.ALL_LANGUAGES.equalsIgnoreCase(lang)) {
194 langSet.add(Constants.ALL_LANGUAGES);
195 } else {
196 final String normalizeLang = systemHelper.normalizeLang(lang);
197 if (normalizeLang != null) {
198 langSet.add(normalizeLang);
199 }
200 }
201 }
202 }
203 if (langSet.size() > 1 && langSet.contains(Constants.ALL_LANGUAGES)) {
204 return new String[] { Constants.ALL_LANGUAGES };
205 } else {
206 langSet.remove(Constants.ALL_LANGUAGES);
207 }
208 return langSet.toArray(new String[langSet.size()]);
209 } else if (fessConfig.isBrowserLocaleForSearchUsed()) {
210 final Set<String> langSet = new HashSet<>();
211 final Enumeration<Locale> locales = request.getLocales();
212 if (locales != null) {
213 while (locales.hasMoreElements()) {
214 final Locale locale = locales.nextElement();
215 final String normalizeLang = systemHelper.normalizeLang(locale.toString());
216 if (normalizeLang != null) {
217 langSet.add(normalizeLang);
218 }
219 }
220 if (!langSet.isEmpty()) {
221 return langSet.toArray(new String[langSet.size()]);
222 }
223 }
224 }
225 return StringUtil.EMPTY_STRINGS;
226 }
227
228 public OptionalEntity<Map<String, Object>> getDocumentByDocId(final String docId, final String[] fields,
229 final OptionalThing<FessUserBean> userBean) {
230 return fessEsClient.getDocument(
231 fessConfig.getIndexDocumentSearchIndex(),
232 fessConfig.getIndexDocumentType(),
233 builder -> {
234 final BoolQueryBuilder boolQuery =
235 QueryBuilders.boolQuery().must(QueryBuilders.termQuery(fessConfig.getIndexFieldDocId(), docId));
236 final Set<String> roleSet = ComponentUtil.getRoleQueryHelper().build(SearchRequestType.JSON);
237 if (!roleSet.isEmpty()) {
238 final BoolQueryBuilder roleQuery = QueryBuilders.boolQuery();
239 roleSet.stream().forEach(name -> {
240 roleQuery.should(QueryBuilders.termQuery(fessConfig.getIndexFieldRole(), name));
241 });
242 boolQuery.filter(roleQuery);
243 }
244 builder.setQuery(boolQuery);
245 builder.setFetchSource(fields, null);
246 fessConfig.processSearchPreference(builder, userBean);
247 return true;
248 });
249
250 }
251
252 public List<Map<String, Object>> getDocumentListByDocIds(final String[] docIds, final String[] fields,
253 final OptionalThing<FessUserBean> userBean, final SearchRequestType searchRequestType) {
254 return fessEsClient.getDocumentList(
255 fessConfig.getIndexDocumentSearchIndex(),
256 fessConfig.getIndexDocumentType(),
257 builder -> {
258 final BoolQueryBuilder boolQuery =
259 QueryBuilders.boolQuery().must(QueryBuilders.termsQuery(fessConfig.getIndexFieldDocId(), docIds));
260 if (searchRequestType != SearchRequestType.ADMIN_SEARCH) {
261 final Set<String> roleSet = ComponentUtil.getRoleQueryHelper().build(searchRequestType);
262 if (!roleSet.isEmpty()) {
263 final BoolQueryBuilder roleQuery = QueryBuilders.boolQuery();
264 roleSet.stream().forEach(name -> {
265 roleQuery.should(QueryBuilders.termQuery(fessConfig.getIndexFieldRole(), name));
266 });
267 boolQuery.filter(roleQuery);
268 }
269 }
270 builder.setQuery(boolQuery);
271 builder.setSize(fessConfig.getPagingSearchPageMaxSizeAsInteger().intValue());
272 builder.setFetchSource(fields, null);
273 fessConfig.processSearchPreference(builder, userBean);
274 return true;
275 });
276 }
277
278 public boolean update(final String id, final String field, final Object value) {
279 return fessEsClient.update(fessConfig.getIndexDocumentUpdateIndex(), fessConfig.getIndexDocumentType(), id, field, value);
280 }
281
282 public boolean update(final String id, final Consumer<UpdateRequestBuilder> builderLambda) {
283 try {
284 final UpdateRequestBuilder builder =
285 fessEsClient.prepareUpdate(fessConfig.getIndexDocumentUpdateIndex(), fessConfig.getIndexDocumentType(), id);
286 builderLambda.accept(builder);
287 final UpdateResponse response = builder.execute().actionGet(fessConfig.getIndexIndexTimeout());
288 return response.getResult() == Result.CREATED || response.getResult() == Result.UPDATED;
289 } catch (final ElasticsearchException e) {
290 throw new FessEsClientException("Failed to update doc " + id, e);
291 }
292 }
293
294 public boolean bulkUpdate(final Consumer<BulkRequestBuilder> consumer) {
295 final BulkRequestBuilder builder = fessEsClient.prepareBulk();
296 consumer.accept(builder);
297 try {
298 final BulkResponse response = builder.execute().get();
299 if (response.hasFailures()) {
300 throw new FessEsClientException(response.buildFailureMessage());
301 } else {
302 return true;
303 }
304 } catch (InterruptedException | ExecutionException e) {
305 throw new FessEsClientException("Failed to update bulk data.", e);
306 }
307 }
308 }