2 * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
\r
4 * This library is free software; you can redistribute it and/or modify it under
\r
5 * the terms of the GNU Lesser General Public License as published by the Free
\r
6 * Software Foundation; either version 2.1 of the License, or (at your option)
\r
9 * This library is distributed in the hope that it will be useful, but WITHOUT
\r
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
\r
11 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
\r
15 package com.liferay.portal.search.solr;
\r
17 import com.liferay.portal.kernel.dao.orm.QueryUtil;
\r
18 import com.liferay.portal.kernel.json.JSONArray;
\r
19 import com.liferay.portal.kernel.json.JSONObject;
\r
20 import com.liferay.portal.kernel.log.Log;
\r
21 import com.liferay.portal.kernel.log.LogFactoryUtil;
\r
22 import com.liferay.portal.kernel.search.Document;
\r
23 import com.liferay.portal.kernel.search.DocumentImpl;
\r
24 import com.liferay.portal.kernel.search.Field;
\r
25 import com.liferay.portal.kernel.search.Hits;
\r
26 import com.liferay.portal.kernel.search.HitsImpl;
\r
27 import com.liferay.portal.kernel.search.IndexSearcher;
\r
28 import com.liferay.portal.kernel.search.Query;
\r
29 import com.liferay.portal.kernel.search.QueryConfig;
\r
30 import com.liferay.portal.kernel.search.QueryTranslatorUtil;
\r
31 import com.liferay.portal.kernel.search.SearchContext;
\r
32 import com.liferay.portal.kernel.search.SearchException;
\r
33 import com.liferay.portal.kernel.search.Sort;
\r
34 import com.liferay.portal.kernel.search.facet.Facet;
\r
35 import com.liferay.portal.kernel.search.facet.RangeFacet;
\r
36 import com.liferay.portal.kernel.search.facet.collector.FacetCollector;
\r
37 import com.liferay.portal.kernel.search.facet.config.FacetConfiguration;
\r
38 import com.liferay.portal.kernel.util.ArrayUtil;
\r
39 import com.liferay.portal.kernel.util.GetterUtil;
\r
40 import com.liferay.portal.kernel.util.LocaleUtil;
\r
41 import com.liferay.portal.kernel.util.StringBundler;
\r
42 import com.liferay.portal.kernel.util.StringPool;
\r
43 import com.liferay.portal.kernel.util.StringUtil;
\r
44 import com.liferay.portal.kernel.util.Time;
\r
45 import com.liferay.portal.kernel.util.Validator;
\r
46 import com.liferay.portal.search.solr.facet.SolrFacetFieldCollector;
\r
47 import com.liferay.portal.search.solr.facet.SolrFacetQueryCollector;
\r
49 import java.util.ArrayList;
\r
50 import java.util.Collection;
\r
51 import java.util.HashSet;
\r
52 import java.util.List;
\r
53 import java.util.Map;
\r
54 import java.util.Set;
\r
55 import java.util.regex.Matcher;
\r
56 import java.util.regex.Pattern;
\r
58 import org.apache.solr.client.solrj.SolrQuery.ORDER;
\r
59 import org.apache.solr.client.solrj.SolrQuery;
\r
60 import org.apache.solr.client.solrj.SolrRequest.METHOD;
\r
61 import org.apache.solr.client.solrj.SolrServer;
\r
62 import org.apache.solr.client.solrj.response.FacetField;
\r
63 import org.apache.solr.client.solrj.response.QueryResponse;
\r
64 import org.apache.solr.common.SolrDocument;
\r
65 import org.apache.solr.common.SolrDocumentList;
\r
66 import org.apache.solr.common.params.FacetParams;
\r
69 * @author Bruno Farache
\r
70 * @author Zsolt Berentey
\r
71 * @author Raymond Augé
\r
73 public class SolrIndexSearcherImpl implements IndexSearcher {
\r
75 public Hits search(SearchContext searchContext, Query query)
\r
76 throws SearchException {
\r
79 return doSearch(searchContext, query);
\r
81 catch (Exception e) {
\r
84 throw new SearchException(e.getMessage());
\r
89 String searchEngineId, long companyId, Query query, Sort[] sorts,
\r
91 throws SearchException {
\r
93 return search(searchEngineId, companyId, query, sorts, new ArrayList<String>(), start, end);
\r
97 String searchEngineId, long companyId, Query query, Sort[] sorts, List<String> filters,
\r
99 throws SearchException {
\r
102 SolrQuery solrQuery = translateQuery(
\r
103 companyId, query, sorts, filters, start, end);
\r
104 _log.debug("Search query "+solrQuery.toString());
\r
105 QueryResponse queryResponse = _solrServer.query(
\r
106 solrQuery, METHOD.POST);
\r
108 boolean allResults = false;
\r
110 if (solrQuery.getRows() == 0) {
\r
115 solrQuery, query, query.getQueryConfig(), queryResponse,
\r
118 catch (Exception e) {
\r
121 if (!_swallowException) {
\r
122 throw new SearchException(e.getMessage());
\r
125 return new HitsImpl();
\r
129 public void setSolrServer(SolrServer solrServer) {
\r
130 _solrServer = solrServer;
\r
133 public void setSwallowException(boolean swallowException) {
\r
134 _swallowException = swallowException;
\r
137 protected Hits doSearch(SearchContext searchContext, Query query)
\r
140 SolrQuery solrQuery = translateQuery(
\r
141 searchContext.getCompanyId(), query, searchContext.getSorts(), new ArrayList<String>(),
\r
142 searchContext.getStart(), searchContext.getEnd());
\r
144 Map<String, Facet> facets = searchContext.getFacets();
\r
146 for (Facet facet : facets.values()) {
\r
147 if (facet.isStatic()) {
\r
151 FacetConfiguration facetConfiguration =
\r
152 facet.getFacetConfiguration();
\r
154 if (facet instanceof RangeFacet) {
\r
155 solrQuery.addFacetField(facetConfiguration.getFieldName());
\r
157 JSONObject dataJSONObject = facetConfiguration.getData();
\r
159 JSONArray rangesJSONArray = dataJSONObject.getJSONArray(
\r
162 if (rangesJSONArray == null) {
\r
166 for (int i = 0; i < rangesJSONArray.length(); i++) {
\r
167 JSONObject rangeJSONObject = rangesJSONArray.getJSONObject(
\r
170 String range = rangeJSONObject.getString("range");
\r
172 String facetQuery =
\r
173 facetConfiguration.getFieldName() +
\r
174 StringPool.COLON + range;
\r
176 solrQuery.addFacetQuery(facetQuery);
\r
180 solrQuery.addFacetField(facetConfiguration.getFieldName());
\r
183 String facetSort = FacetParams.FACET_SORT_COUNT;
\r
185 String order = facetConfiguration.getOrder();
\r
187 if (order.equals("OrderValueAsc")) {
\r
188 facetSort = FacetParams.FACET_SORT_INDEX;
\r
192 "f." + facetConfiguration.getFieldName() + ".facet.sort",
\r
196 solrQuery.setFacetLimit(-1);
\r
198 QueryResponse queryResponse = _solrServer.query(solrQuery, METHOD.POST);
\r
200 boolean allResults = false;
\r
202 if (solrQuery.getRows() == 0) {
\r
206 List<FacetField> facetFields = queryResponse.getFacetFields();
\r
208 if (facetFields != null) {
\r
209 for (FacetField facetField : facetFields) {
\r
210 Facet facet = facets.get(facetField.getName());
\r
212 FacetCollector facetCollector = null;
\r
214 if (facet instanceof RangeFacet) {
\r
215 facetCollector = new SolrFacetQueryCollector(
\r
216 facetField.getName(), queryResponse.getFacetQuery());
\r
219 facetCollector = new SolrFacetFieldCollector(
\r
220 facetField.getName(), facetField);
\r
223 facet.setFacetCollector(facetCollector);
\r
228 solrQuery, query, query.getQueryConfig(), queryResponse,
\r
232 protected String getSnippet(
\r
233 SolrDocument solrDocument, QueryConfig queryConfig,
\r
234 Set<String> queryTerms,
\r
235 Map<String, Map<String, List<String>>> highlights, String field) {
\r
237 if (highlights == null) {
\r
238 return StringPool.BLANK;
\r
241 String key = (String)solrDocument.getFieldValue(Field.UID);
\r
243 Map<String, List<String>> uidHighlights = highlights.get(key);
\r
245 boolean localizedSearch = true;
\r
247 String defaultLanguageId = LocaleUtil.toLanguageId(
\r
248 LocaleUtil.getDefault());
\r
249 String queryLanguageId = LocaleUtil.toLanguageId(
\r
250 queryConfig.getLocale());
\r
252 if (defaultLanguageId.equals(queryLanguageId)) {
\r
253 localizedSearch = false;
\r
256 if (localizedSearch) {
\r
257 String localizedName = DocumentImpl.getLocalizedName(
\r
258 queryConfig.getLocale(), field);
\r
260 if (solrDocument.containsKey(localizedName)) {
\r
261 field = localizedName;
\r
265 List<String> snippets = uidHighlights.get(field);
\r
267 String snippet = StringUtil.merge(snippets, "...");
\r
269 if (Validator.isNotNull(snippet)) {
\r
270 snippet = snippet + "...";
\r
273 snippet = StringPool.BLANK;
\r
276 Pattern pattern = Pattern.compile("<em>(.*?)</em>");
\r
278 Matcher matcher = pattern.matcher(snippet);
\r
280 while (matcher.find()) {
\r
281 queryTerms.add(matcher.group(1));
\r
284 snippet = StringUtil.replace(snippet, "<em>", "");
\r
285 snippet = StringUtil.replace(snippet, "</em>", "");
\r
290 protected Hits subset(
\r
291 SolrQuery solrQuery, Query query, QueryConfig queryConfig,
\r
292 QueryResponse queryResponse, boolean allResults)
\r
295 long startTime = System.currentTimeMillis();
\r
297 Hits hits = new HitsImpl();
\r
299 SolrDocumentList solrDocumentList = queryResponse.getResults();
\r
301 long total = solrDocumentList.getNumFound();
\r
303 if (allResults && (total > 0)) {
\r
304 solrQuery.setRows((int)total);
\r
306 queryResponse = _solrServer.query(solrQuery);
\r
308 return subset(solrQuery, query, queryConfig, queryResponse, false);
\r
311 List<Document> documents = new ArrayList<Document>();
\r
312 List<Float> scores = new ArrayList<Float>();
\r
313 List<String> snippets = new ArrayList<String>();
\r
315 float maxScore = -1;
\r
316 Set<String> queryTerms = new HashSet<String>();
\r
317 int subsetTotal = 0;
\r
319 for (SolrDocument solrDocument : solrDocumentList) {
\r
320 Document document = new DocumentImpl();
\r
322 Collection<String> names = solrDocument.getFieldNames();
\r
324 for (String name : names) {
\r
325 Collection<Object> fieldValues = solrDocument.getFieldValues(
\r
328 Field field = new Field(
\r
330 ArrayUtil.toStringArray(
\r
331 fieldValues.toArray(new Object[fieldValues.size()])));
\r
333 document.add(field);
\r
336 documents.add(document);
\r
338 String snippet = StringPool.BLANK;
\r
340 if (queryConfig.isHighlightEnabled()) {
\r
341 snippet = getSnippet(
\r
342 solrDocument, queryConfig, queryTerms,
\r
343 queryResponse.getHighlighting(), Field.CONTENT);
\r
345 if (Validator.isNull(snippet)) {
\r
346 snippet = getSnippet(
\r
347 solrDocument, queryConfig, queryTerms,
\r
348 queryResponse.getHighlighting(), Field.TITLE);
\r
351 if (Validator.isNotNull(snippet)) {
\r
352 snippets.add(snippet);
\r
356 if (queryConfig.isScoreEnabled()) {
\r
357 float score = GetterUtil.getFloat(
\r
358 String.valueOf(solrDocument.getFieldValue("score")));
\r
360 if (score > maxScore) {
\r
367 scores.add(maxScore);
\r
373 hits.setDocs(documents.toArray(new Document[subsetTotal]));
\r
374 hits.setLength((int)total);
\r
375 hits.setQuery(query);
\r
376 hits.setQueryTerms(queryTerms.toArray(new String[queryTerms.size()]));
\r
378 Float[] scoresArray = scores.toArray(new Float[subsetTotal]);
\r
380 if (queryConfig.isScoreEnabled() && (subsetTotal > 0) &&
\r
383 for (int i = 0; i < scoresArray.length; i++) {
\r
384 scoresArray[i] = scoresArray[i] / maxScore;
\r
388 hits.setScores(scoresArray);
\r
391 (float)(System.currentTimeMillis() - startTime) / Time.SECOND;
\r
393 hits.setSearchTime(searchTime);
\r
394 hits.setSnippets(snippets.toArray(new String[subsetTotal]));
\r
395 hits.setStart(startTime);
\r
400 protected SolrQuery translateQuery(
\r
401 long companyId, Query query, Sort[] sorts, List<String> filters, int start, int end)
\r
404 QueryConfig queryConfig = query.getQueryConfig();
\r
406 SolrQuery solrQuery = new SolrQuery();
\r
408 if (queryConfig.isHighlightEnabled()) {
\r
409 solrQuery.setHighlight(true);
\r
410 solrQuery.setHighlightFragsize(
\r
411 queryConfig.getHighlightFragmentSize());
\r
412 solrQuery.setHighlightSnippets(
\r
413 queryConfig.getHighlightSnippetSize());
\r
415 String localizedContentName = DocumentImpl.getLocalizedName(
\r
416 queryConfig.getLocale(), Field.CONTENT);
\r
418 String localizedTitleName = DocumentImpl.getLocalizedName(
\r
419 queryConfig.getLocale(), Field.TITLE);
\r
421 solrQuery.setParam(
\r
422 "hl.fl", Field.CONTENT, localizedContentName, Field.TITLE,
\r
423 localizedTitleName);
\r
425 for (String filterQuery : filters) {
\r
426 solrQuery.addFilterQuery(filterQuery);
\r
430 solrQuery.setIncludeScore(queryConfig.isScoreEnabled());
\r
432 QueryTranslatorUtil.translateForSolr(query);
\r
434 String queryString = query.toString();
\r
436 StringBundler sb = new StringBundler(6);
\r
438 sb.append(queryString);
\r
439 sb.append(StringPool.SPACE);
\r
440 sb.append(StringPool.PLUS);
\r
441 sb.append(Field.COMPANY_ID);
\r
442 sb.append(StringPool.COLON);
\r
443 sb.append(companyId);
\r
445 solrQuery.setQuery(sb.toString());
\r
447 if ((start == QueryUtil.ALL_POS) && (end == QueryUtil.ALL_POS)) {
\r
448 solrQuery.setRows(0);
\r
451 solrQuery.setRows(end - start);
\r
452 solrQuery.setStart(start);
\r
455 if (sorts != null) {
\r
456 for (Sort sort : sorts) {
\r
457 if (sort == null) {
\r
461 String sortFieldName = sort.getFieldName();
\r
463 if (DocumentImpl.isSortableTextField(sortFieldName)) {
\r
464 sortFieldName = DocumentImpl.getSortableFieldName(
\r
468 ORDER order = ORDER.asc;
\r
470 if (Validator.isNull(sortFieldName) ||
\r
471 !sortFieldName.endsWith("sortable")) {
\r
473 sortFieldName = "score";
\r
475 order = ORDER.desc;
\r
478 if (sort.isReverse()) {
\r
479 order = ORDER.desc;
\r
482 solrQuery.addSortField(sortFieldName, order);
\r
489 private static Log _log = LogFactoryUtil.getLog(
\r
490 SolrIndexSearcherImpl.class);
\r
492 private SolrServer _solrServer;
\r
493 private boolean _swallowException;
\r