/*
* Symphony - A modern community (forum/SNS/blog) platform written in Java.
* Copyright (C) 2012-2017, b3log.org & hacpai.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.b3log.symphony.service;
import org.b3log.latke.logging.Level;
import org.b3log.latke.logging.Logger;
import org.b3log.latke.service.annotation.Service;
import org.b3log.latke.servlet.HTTPRequestMethod;
import org.b3log.latke.urlfetch.*;
import org.b3log.symphony.model.Article;
import org.b3log.symphony.util.Symphonys;
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.URL;
import java.net.URLEncoder;
import java.net.UnknownHostException;
/**
* Search query service.
*
* Uses <a href="https://www.elastic.co/products/elasticsearch">Elasticsearch</a> as the underlying engine. Uses
* <a href="https://www.algolia.com">Algolia</a> as the underlying engine.
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.2.1.1, Apr 1, 2016
* @since 1.4.0
*/
@Service
public class SearchQueryService {
/**
* Logger.
*/
private static final Logger LOGGER = Logger.getLogger(SearchQueryService.class.getName());
/**
* URL fetch service.
*/
private static final URLFetchService URL_FETCH_SVC = URLFetchServiceFactory.getURLFetchService();
/**
* Searches by Elasticsearch.
*
* @param type the specified document type
* @param keyword the specified keyword
* @param currentPage the specified current page number
* @param pageSize the specified page size
* @return search result, returns {@code null} if not found
*/
public JSONObject searchElasticsearch(final String type, final String keyword, final int currentPage, final int pageSize) {
final HTTPRequest request = new HTTPRequest();
request.setRequestMethod(HTTPRequestMethod.POST);
try {
request.setURL(new URL(SearchMgmtService.ES_SERVER + "/" + SearchMgmtService.ES_INDEX_NAME + "/" + type
+ "/_search"));
final JSONObject reqData = new JSONObject();
final JSONObject q = new JSONObject();
final JSONObject and = new JSONObject();
q.put("and", and);
final JSONArray query = new JSONArray();
and.put("query", query);
final JSONObject or = new JSONObject();
query.put(or);
final JSONArray orClause = new JSONArray();
or.put("or", orClause);
final JSONObject content = new JSONObject();
content.put(Article.ARTICLE_CONTENT, keyword);
final JSONObject matchContent = new JSONObject();
matchContent.put("match", content);
orClause.put(matchContent);
final JSONObject title = new JSONObject();
title.put(Article.ARTICLE_TITLE, keyword);
final JSONObject matchTitle = new JSONObject();
matchTitle.put("match", title);
orClause.put(matchTitle);
reqData.put("query", q);
reqData.put("from", currentPage);
reqData.put("size", pageSize);
final JSONArray sort = new JSONArray();
final JSONObject sortField = new JSONObject();
sort.put(sortField);
sortField.put(Article.ARTICLE_CREATE_TIME, "desc");
sort.put("_score");
reqData.put("sort", sort);
final JSONObject highlight = new JSONObject();
reqData.put("highlight", highlight);
highlight.put("number_of_fragments", 3);
highlight.put("fragment_size", 150);
final JSONObject fields = new JSONObject();
highlight.put("fields", fields);
final JSONObject contentField = new JSONObject();
fields.put(Article.ARTICLE_CONTENT, contentField);
final JSONArray filter = new JSONArray();
and.put("filter", filter);
final JSONObject term = new JSONObject();
filter.put(term);
final JSONObject field = new JSONObject();
term.put("term", field);
field.put(Article.ARTICLE_STATUS, Article.ARTICLE_STATUS_C_VALID);
LOGGER.debug(reqData.toString(4));
request.setPayload(reqData.toString().getBytes("UTF-8"));
final HTTPResponse response = URL_FETCH_SVC.fetch(request);
return new JSONObject(new String(response.getContent(), "UTF-8"));
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Queries failed", e);
return null;
}
}
/**
* Searches by Algolia.
*
* @param keyword the specified keyword
* @param currentPage the specified current page number
* @param pageSize the specified page size
* @return search result, returns {@code null} if not found
*/
public JSONObject searchAlgolia(final String keyword, final int currentPage, final int pageSize) {
final int maxRetries = 3;
int retries = 1;
final String appId = Symphonys.get("algolia.appId");
final String index = Symphonys.get("algolia.index");
final String key = Symphonys.get("algolia.adminKey");
while (retries <= maxRetries) {
String host = appId + "-" + retries + ".algolianet.com";
try {
final HTTPRequest request = new HTTPRequest();
request.addHeader(new HTTPHeader("X-Algolia-API-Key", key));
request.addHeader(new HTTPHeader("X-Algolia-Application-Id", appId));
request.setRequestMethod(HTTPRequestMethod.POST);
request.setURL(new URL("https://" + host + "/1/indexes/" + index + "/query"));
final JSONObject params = new JSONObject();
params.put("params", "query=" + URLEncoder.encode(keyword, "UTF-8")
+ "&getRankingInfo=1&facets=*&attributesToRetrieve=*&highlightPreTag=%3Cem%3E"
+ "&highlightPostTag=%3C%2Fem%3E"
+ "&facetFilters=%5B%5D&maxValuesPerFacet=100"
+ "&hitsPerPage=" + pageSize + "&page=" + (currentPage - 1));
request.setPayload(params.toString().getBytes("UTF-8"));
final HTTPResponse response = URL_FETCH_SVC.fetch(request);
final JSONObject ret = new JSONObject(new String(response.getContent(), "UTF-8"));
if (200 != response.getResponseCode()) {
LOGGER.warn(ret.toString(4));
return null;
}
return ret;
} catch (final UnknownHostException e) {
LOGGER.log(Level.ERROR, "Queries failed [UnknownHostException=" + host + "]");
retries++;
if (retries > maxRetries) {
LOGGER.log(Level.ERROR, "Queries failed [UnknownHostException]");
}
} catch (final Exception e) {
LOGGER.log(Level.ERROR, "Queries failed", e);
break;
}
}
return null;
}
}