JsonApiManager.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  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.api.json;
  17. import java.io.IOException;
  18. import java.net.URLDecoder;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Locale;
  24. import java.util.Map;
  25. import javax.annotation.PostConstruct;
  26. import javax.servlet.FilterChain;
  27. import javax.servlet.ServletException;
  28. import javax.servlet.http.HttpServletRequest;
  29. import javax.servlet.http.HttpServletResponse;
  30. import org.apache.logging.log4j.LogManager;
  31. import org.apache.logging.log4j.Logger;
  32. import org.codelibs.core.exception.IORuntimeException;
  33. import org.codelibs.core.lang.StringUtil;
  34. import org.codelibs.fesen.script.Script;
  35. import org.codelibs.fess.Constants;
  36. import org.codelibs.fess.api.BaseJsonApiManager;
  37. import org.codelibs.fess.app.service.FavoriteLogService;
  38. import org.codelibs.fess.entity.FacetInfo;
  39. import org.codelibs.fess.entity.GeoInfo;
  40. import org.codelibs.fess.entity.HighlightInfo;
  41. import org.codelibs.fess.entity.PingResponse;
  42. import org.codelibs.fess.entity.SearchRenderData;
  43. import org.codelibs.fess.entity.SearchRequestParams;
  44. import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
  45. import org.codelibs.fess.es.client.SearchEngineClient;
  46. import org.codelibs.fess.exception.WebApiException;
  47. import org.codelibs.fess.helper.LabelTypeHelper;
  48. import org.codelibs.fess.helper.PopularWordHelper;
  49. import org.codelibs.fess.helper.RelatedContentHelper;
  50. import org.codelibs.fess.helper.RelatedQueryHelper;
  51. import org.codelibs.fess.helper.SearchHelper;
  52. import org.codelibs.fess.helper.SystemHelper;
  53. import org.codelibs.fess.helper.UserInfoHelper;
  54. import org.codelibs.fess.mylasta.direction.FessConfig;
  55. import org.codelibs.fess.util.ComponentUtil;
  56. import org.codelibs.fess.util.DocumentUtil;
  57. import org.codelibs.fess.util.FacetResponse;
  58. import org.codelibs.fess.util.FacetResponse.Field;
  59. import org.dbflute.optional.OptionalThing;
  60. public class JsonApiManager extends BaseJsonApiManager {
  61. private static final Logger logger = LogManager.getLogger(JsonApiManager.class);
  62. public JsonApiManager() {
  63. setPathPrefix("/json");
  64. }
  65. @PostConstruct
  66. public void register() {
  67. if (logger.isInfoEnabled()) {
  68. logger.info("Load {}", this.getClass().getSimpleName());
  69. }
  70. ComponentUtil.getWebApiManagerFactory().add(this);
  71. }
  72. @Override
  73. public boolean matches(final HttpServletRequest request) {
  74. final FessConfig fessConfig = ComponentUtil.getFessConfig();
  75. if (!fessConfig.isWebApiJson()) {
  76. switch (getFormatType(request)) {
  77. case SEARCH:
  78. case LABEL:
  79. case POPULARWORD:
  80. return false;
  81. default:
  82. break;
  83. }
  84. }
  85. final String servletPath = request.getServletPath();
  86. return servletPath.startsWith(pathPrefix);
  87. }
  88. @Override
  89. public void process(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain)
  90. throws IOException, ServletException {
  91. switch (getFormatType(request)) {
  92. case SEARCH:
  93. processSearchRequest(request, response, chain);
  94. break;
  95. case LABEL:
  96. processLabelRequest(request, response, chain);
  97. break;
  98. case POPULARWORD:
  99. processPopularWordRequest(request, response, chain);
  100. break;
  101. case FAVORITE:
  102. processFavoriteRequest(request, response, chain);
  103. break;
  104. case FAVORITES:
  105. processFavoritesRequest(request, response, chain);
  106. break;
  107. case PING:
  108. processPingRequest(request, response, chain);
  109. break;
  110. case SCROLL:
  111. processScrollSearchRequest(request, response, chain);
  112. break;
  113. default:
  114. writeJsonResponse(99, StringUtil.EMPTY, "Not found.");
  115. break;
  116. }
  117. }
  118. protected void processScrollSearchRequest(final HttpServletRequest request, final HttpServletResponse response,
  119. final FilterChain chain) {
  120. final SearchHelper searchHelper = ComponentUtil.getSearchHelper();
  121. final FessConfig fessConfig = ComponentUtil.getFessConfig();
  122. if (!fessConfig.isAcceptedSearchReferer(request.getHeader("referer"))) {
  123. writeJsonResponse(99, StringUtil.EMPTY, "Referer is invalid.");
  124. return;
  125. }
  126. if (!fessConfig.isApiSearchScroll()) {
  127. writeJsonResponse(99, StringUtil.EMPTY, "Scroll Search is not available.");
  128. return;
  129. }
  130. final StringBuilder buf = new StringBuilder(1000);
  131. request.setAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, Constants.SEARCH_LOG_ACCESS_TYPE_JSON);
  132. final JsonRequestParams params = new JsonRequestParams(request, fessConfig);
  133. try {
  134. response.setContentType("application/x-ndjson; charset=UTF-8");
  135. final long count = searchHelper.scrollSearch(params, doc -> {
  136. buf.setLength(0);
  137. buf.append('{');
  138. boolean first2 = true;
  139. for (final Map.Entry<String, Object> entry : doc.entrySet()) {
  140. final String name = entry.getKey();
  141. if (StringUtil.isNotBlank(name) && entry.getValue() != null) {
  142. if (!first2) {
  143. buf.append(',');
  144. } else {
  145. first2 = false;
  146. }
  147. buf.append(escapeJson(name));
  148. buf.append(':');
  149. buf.append(escapeJson(entry.getValue()));
  150. }
  151. }
  152. buf.append('}');
  153. buf.append('\n');
  154. try {
  155. response.getWriter().print(buf.toString());
  156. } catch (final IOException e) {
  157. throw new IORuntimeException(e);
  158. }
  159. return true;
  160. }, OptionalThing.empty());
  161. response.flushBuffer();
  162. if (logger.isDebugEnabled()) {
  163. logger.debug("Loaded {} docs", count);
  164. }
  165. } catch (final Exception e) {
  166. final int status = 9;
  167. if (logger.isDebugEnabled()) {
  168. logger.debug("Failed to process a ping request.", e);
  169. }
  170. writeJsonResponse(status, null, e);
  171. }
  172. }
  173. protected void processPingRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) {
  174. final SearchEngineClient searchEngineClient = ComponentUtil.getSearchEngineClient();
  175. int status;
  176. Exception err = null;
  177. try {
  178. final PingResponse pingResponse = searchEngineClient.ping();
  179. status = pingResponse.getStatus();
  180. writeJsonResponse(status, "\"message\":" + pingResponse.getMessage());
  181. } catch (final Exception e) {
  182. status = 9;
  183. err = e;
  184. if (logger.isDebugEnabled()) {
  185. logger.debug("Failed to process a ping request.", e);
  186. }
  187. writeJsonResponse(status, null, err);
  188. }
  189. }
  190. protected void processSearchRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) {
  191. final SearchHelper searchHelper = ComponentUtil.getSearchHelper();
  192. final FessConfig fessConfig = ComponentUtil.getFessConfig();
  193. final RelatedQueryHelper relatedQueryHelper = ComponentUtil.getRelatedQueryHelper();
  194. final RelatedContentHelper relatedContentHelper = ComponentUtil.getRelatedContentHelper();
  195. int status = 0;
  196. Exception err = null;
  197. String query = null;
  198. final StringBuilder buf = new StringBuilder(1000); // TODO replace response stream
  199. request.setAttribute(Constants.SEARCH_LOG_ACCESS_TYPE, Constants.SEARCH_LOG_ACCESS_TYPE_JSON);
  200. try {
  201. final SearchRenderData data = new SearchRenderData();
  202. final JsonRequestParams params = new JsonRequestParams(request, fessConfig);
  203. query = params.getQuery();
  204. searchHelper.search(params, data, OptionalThing.empty());
  205. final String execTime = data.getExecTime();
  206. final String queryTime = Long.toString(data.getQueryTime());
  207. final String pageSize = Integer.toString(data.getPageSize());
  208. final String currentPageNumber = Integer.toString(data.getCurrentPageNumber());
  209. final String allRecordCount = Long.toString(data.getAllRecordCount());
  210. final String allRecordCountRelation = data.getAllRecordCountRelation();
  211. final String allPageCount = Integer.toString(data.getAllPageCount());
  212. final List<Map<String, Object>> documentItems = data.getDocumentItems();
  213. final FacetResponse facetResponse = data.getFacetResponse();
  214. final String queryId = data.getQueryId();
  215. final String highlightParams = data.getAppendHighlightParams();
  216. final boolean nextPage = data.isExistNextPage();
  217. final boolean prevPage = data.isExistPrevPage();
  218. final long startRecordNumber = data.getCurrentStartRecordNumber();
  219. final long endRecordNumber = data.getCurrentEndRecordNumber();
  220. final List<String> pageNumbers = data.getPageNumberList();
  221. final boolean partial = data.isPartialResults();
  222. final String searchQuery = data.getSearchQuery();
  223. final long requestedTime = data.getRequestedTime();
  224. buf.append("\"q\":");
  225. buf.append(escapeJson(query));
  226. buf.append(",\"query_id\":");
  227. buf.append(escapeJson(queryId));
  228. buf.append(",\"exec_time\":");
  229. buf.append(execTime);
  230. buf.append(",\"query_time\":");
  231. buf.append(queryTime);
  232. buf.append(',');
  233. buf.append("\"page_size\":");
  234. buf.append(pageSize);
  235. buf.append(',');
  236. buf.append("\"page_number\":");
  237. buf.append(currentPageNumber);
  238. buf.append(',');
  239. buf.append("\"record_count\":");
  240. buf.append(allRecordCount);
  241. buf.append(',');
  242. buf.append("\"record_count_relation\":");
  243. buf.append(escapeJson(allRecordCountRelation));
  244. buf.append(',');
  245. buf.append("\"page_count\":");
  246. buf.append(allPageCount);
  247. buf.append(",\"highlight_params\":");
  248. buf.append(escapeJson(highlightParams));
  249. buf.append(",\"next_page\":");
  250. buf.append(escapeJson(nextPage));
  251. buf.append(",\"prev_page\":");
  252. buf.append(escapeJson(prevPage));
  253. buf.append(",\"start_record_number\":");
  254. buf.append(startRecordNumber);
  255. buf.append(",\"end_record_number\":");
  256. buf.append(escapeJson(endRecordNumber));
  257. buf.append(",\"page_numbers\":");
  258. buf.append(escapeJson(pageNumbers));
  259. buf.append(",\"partial\":");
  260. buf.append(escapeJson(partial));
  261. buf.append(",\"search_query\":");
  262. buf.append(escapeJson(searchQuery));
  263. buf.append(",\"requested_time\":");
  264. buf.append(requestedTime);
  265. final String[] relatedQueries = relatedQueryHelper.getRelatedQueries(params.getQuery());
  266. buf.append(",\"related_query\":");
  267. buf.append(escapeJson(relatedQueries));
  268. final String[] relatedContents = relatedContentHelper.getRelatedContents(params.getQuery());
  269. buf.append(",\"related_contents\":");
  270. buf.append(escapeJson(relatedContents));
  271. buf.append(',');
  272. buf.append("\"result\":[");
  273. if (!documentItems.isEmpty()) {
  274. boolean first1 = true;
  275. for (final Map<String, Object> document : documentItems) {
  276. if (!first1) {
  277. buf.append(',');
  278. } else {
  279. first1 = false;
  280. }
  281. buf.append('{');
  282. boolean first2 = true;
  283. for (final Map.Entry<String, Object> entry : document.entrySet()) {
  284. final String name = entry.getKey();
  285. if (StringUtil.isNotBlank(name) && entry.getValue() != null
  286. && ComponentUtil.getQueryHelper().isApiResponseField(name)) {
  287. if (!first2) {
  288. buf.append(',');
  289. } else {
  290. first2 = false;
  291. }
  292. buf.append(escapeJson(name));
  293. buf.append(':');
  294. buf.append(escapeJson(entry.getValue()));
  295. }
  296. }
  297. buf.append('}');
  298. }
  299. }
  300. buf.append(']');
  301. if (facetResponse != null && facetResponse.hasFacetResponse()) {
  302. // facet field
  303. buf.append(',');
  304. buf.append("\"facet_field\":[");
  305. if (facetResponse.getFieldList() != null) {
  306. boolean first1 = true;
  307. for (final Field field : facetResponse.getFieldList()) {
  308. if (!first1) {
  309. buf.append(',');
  310. } else {
  311. first1 = false;
  312. }
  313. buf.append("{\"name\":");
  314. buf.append(escapeJson(field.getName()));
  315. buf.append(",\"result\":[");
  316. boolean first2 = true;
  317. for (final Map.Entry<String, Long> entry : field.getValueCountMap().entrySet()) {
  318. if (!first2) {
  319. buf.append(',');
  320. } else {
  321. first2 = false;
  322. }
  323. buf.append("{\"value\":");
  324. buf.append(escapeJson(entry.getKey()));
  325. buf.append(",\"count\":");
  326. buf.append(entry.getValue());
  327. buf.append('}');
  328. }
  329. buf.append(']');
  330. buf.append('}');
  331. }
  332. }
  333. buf.append(']');
  334. // facet q
  335. buf.append(',');
  336. buf.append("\"facet_query\":[");
  337. if (facetResponse.getQueryCountMap() != null) {
  338. boolean first1 = true;
  339. for (final Map.Entry<String, Long> entry : facetResponse.getQueryCountMap().entrySet()) {
  340. if (!first1) {
  341. buf.append(',');
  342. } else {
  343. first1 = false;
  344. }
  345. buf.append("{\"value\":");
  346. buf.append(escapeJson(entry.getKey()));
  347. buf.append(",\"count\":");
  348. buf.append(entry.getValue());
  349. buf.append('}');
  350. }
  351. }
  352. buf.append(']');
  353. }
  354. } catch (final Exception e) {
  355. status = 1;
  356. err = e;
  357. if (logger.isDebugEnabled()) {
  358. logger.debug("Failed to process a search request.", e);
  359. }
  360. }
  361. writeJsonResponse(status, buf.toString(), err);
  362. }
  363. protected String detailedMessage(final Throwable t) {
  364. if (t == null) {
  365. return "Unknown";
  366. }
  367. Throwable target = t;
  368. if (target.getCause() == null) {
  369. return target.getClass().getSimpleName() + "[" + target.getMessage() + "]";
  370. }
  371. final StringBuilder sb = new StringBuilder();
  372. while (target != null) {
  373. sb.append(target.getClass().getSimpleName());
  374. if (target.getMessage() != null) {
  375. sb.append("[");
  376. sb.append(target.getMessage());
  377. sb.append("]");
  378. }
  379. sb.append("; ");
  380. target = target.getCause();
  381. if (target != null) {
  382. sb.append("nested: ");
  383. }
  384. }
  385. return sb.toString();
  386. }
  387. protected void processLabelRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) {
  388. final LabelTypeHelper labelTypeHelper = ComponentUtil.getLabelTypeHelper();
  389. int status = 0;
  390. Exception err = null;
  391. final StringBuilder buf = new StringBuilder(255); // TODO replace response stream
  392. try {
  393. final List<Map<String, String>> labelTypeItems = labelTypeHelper.getLabelTypeItemList(SearchRequestType.JSON);
  394. buf.append("\"record_count\":");
  395. buf.append(labelTypeItems.size());
  396. if (!labelTypeItems.isEmpty()) {
  397. buf.append(',');
  398. buf.append("\"result\":[");
  399. boolean first1 = true;
  400. for (final Map<String, String> labelMap : labelTypeItems) {
  401. if (!first1) {
  402. buf.append(',');
  403. } else {
  404. first1 = false;
  405. }
  406. buf.append("{\"label\":");
  407. buf.append(escapeJson(labelMap.get(Constants.ITEM_LABEL)));
  408. buf.append(", \"value\":");
  409. buf.append(escapeJson(labelMap.get(Constants.ITEM_VALUE)));
  410. buf.append('}');
  411. }
  412. buf.append(']');
  413. }
  414. } catch (final Exception e) {
  415. status = 1;
  416. err = e;
  417. if (logger.isDebugEnabled()) {
  418. logger.debug("Failed to process a label request.", e);
  419. }
  420. }
  421. writeJsonResponse(status, buf.toString(), err);
  422. }
  423. protected void processPopularWordRequest(final HttpServletRequest request, final HttpServletResponse response,
  424. final FilterChain chain) {
  425. if (!ComponentUtil.getFessConfig().isWebApiPopularWord()) {
  426. writeJsonResponse(9, null, "Unsupported operation.");
  427. return;
  428. }
  429. final String seed = request.getParameter("seed");
  430. final List<String> tagList = new ArrayList<>();
  431. final String[] tags = request.getParameterValues("labels");
  432. if (tags != null) {
  433. tagList.addAll(Arrays.asList(tags));
  434. }
  435. final String key = ComponentUtil.getVirtualHostHelper().getVirtualHostKey();
  436. if (StringUtil.isNotBlank(key)) {
  437. tagList.add(key);
  438. }
  439. final String[] fields = request.getParameterValues("fields");
  440. final String[] excludes = StringUtil.EMPTY_STRINGS;// TODO
  441. final PopularWordHelper popularWordHelper = ComponentUtil.getPopularWordHelper();
  442. int status = 0;
  443. Exception err = null;
  444. final StringBuilder buf = new StringBuilder(255); // TODO replace response stream
  445. try {
  446. final List<String> popularWordList = popularWordHelper.getWordList(SearchRequestType.JSON, seed,
  447. tagList.toArray(new String[tagList.size()]), null, fields, excludes);
  448. buf.append("\"result\":[");
  449. boolean first1 = true;
  450. for (final String word : popularWordList) {
  451. if (!first1) {
  452. buf.append(',');
  453. } else {
  454. first1 = false;
  455. }
  456. buf.append(escapeJson(word));
  457. }
  458. buf.append(']');
  459. } catch (final Exception e) {
  460. if (e instanceof WebApiException) {
  461. status = ((WebApiException) e).getStatusCode();
  462. } else {
  463. status = 1;
  464. }
  465. err = e;
  466. if (logger.isDebugEnabled()) {
  467. logger.debug("Failed to process a popularWord request.", e);
  468. }
  469. }
  470. writeJsonResponse(status, buf.toString(), err);
  471. }
  472. protected void processFavoriteRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) {
  473. if (!ComponentUtil.getFessConfig().isUserFavorite()) {
  474. writeJsonResponse(9, null, "Unsupported operation.");
  475. return;
  476. }
  477. final FessConfig fessConfig = ComponentUtil.getFessConfig();
  478. final UserInfoHelper userInfoHelper = ComponentUtil.getUserInfoHelper();
  479. final SearchHelper searchHelper = ComponentUtil.getSearchHelper();
  480. final FavoriteLogService favoriteLogService = ComponentUtil.getComponent(FavoriteLogService.class);
  481. final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
  482. try {
  483. final String docId = request.getParameter("docId");
  484. final String queryId = request.getParameter("queryId");
  485. final String[] docIds = userInfoHelper.getResultDocIds(URLDecoder.decode(queryId, Constants.UTF_8));
  486. if (docIds == null) {
  487. throw new WebApiException(6, "No searched urls.");
  488. }
  489. searchHelper.getDocumentByDocId(docId, new String[] { fessConfig.getIndexFieldUrl(), fessConfig.getIndexFieldLang() },
  490. OptionalThing.empty()).ifPresent(doc -> {
  491. final String favoriteUrl = DocumentUtil.getValue(doc, fessConfig.getIndexFieldUrl(), String.class);
  492. final String userCode = userInfoHelper.getUserCode();
  493. if (StringUtil.isBlank(userCode)) {
  494. throw new WebApiException(2, "No user session.");
  495. }
  496. if (StringUtil.isBlank(favoriteUrl)) {
  497. throw new WebApiException(2, "URL is null.");
  498. }
  499. boolean found = false;
  500. for (final String id : docIds) {
  501. if (docId.equals(id)) {
  502. found = true;
  503. break;
  504. }
  505. }
  506. if (!found) {
  507. throw new WebApiException(5, "Not found: " + favoriteUrl);
  508. }
  509. if (!favoriteLogService.addUrl(userCode, (userInfo, favoriteLog) -> {
  510. favoriteLog.setUserInfoId(userInfo.getId());
  511. favoriteLog.setUrl(favoriteUrl);
  512. favoriteLog.setDocId(docId);
  513. favoriteLog.setQueryId(queryId);
  514. favoriteLog.setCreatedAt(systemHelper.getCurrentTimeAsLocalDateTime());
  515. })) {
  516. throw new WebApiException(4, "Failed to add url: " + favoriteUrl);
  517. }
  518. final String id = DocumentUtil.getValue(doc, fessConfig.getIndexFieldId(), String.class);
  519. searchHelper.update(id, builder -> {
  520. final Script script = ComponentUtil.getLanguageHelper().createScript(doc,
  521. "ctx._source." + fessConfig.getIndexFieldFavoriteCount() + "+=1");
  522. builder.setScript(script);
  523. final Map<String, Object> upsertMap = new HashMap<>();
  524. upsertMap.put(fessConfig.getIndexFieldFavoriteCount(), 1);
  525. builder.setUpsert(upsertMap);
  526. builder.setRefreshPolicy(Constants.TRUE);
  527. });
  528. writeJsonResponse(0, "\"result\":\"ok\"", (String) null);
  529. }).orElse(() -> {
  530. throw new WebApiException(6, "Not found: " + docId);
  531. });
  532. } catch (final Exception e) {
  533. int status;
  534. if (e instanceof WebApiException) {
  535. status = ((WebApiException) e).getStatusCode();
  536. } else {
  537. status = 1;
  538. }
  539. writeJsonResponse(status, null, e);
  540. if (logger.isDebugEnabled()) {
  541. logger.debug("Failed to process a favorite request.", e);
  542. }
  543. }
  544. }
  545. protected void processFavoritesRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) {
  546. if (!ComponentUtil.getFessConfig().isUserFavorite()) {
  547. writeJsonResponse(9, null, "Unsupported operation.");
  548. return;
  549. }
  550. final UserInfoHelper userInfoHelper = ComponentUtil.getUserInfoHelper();
  551. final FessConfig fessConfig = ComponentUtil.getFessConfig();
  552. final SearchHelper searchHelper = ComponentUtil.getSearchHelper();
  553. final FavoriteLogService favoriteLogService = ComponentUtil.getComponent(FavoriteLogService.class);
  554. int status = 0;
  555. String body = null;
  556. Exception err = null;
  557. try {
  558. final String queryId = request.getParameter("queryId");
  559. final String userCode = userInfoHelper.getUserCode();
  560. if (StringUtil.isBlank(userCode)) {
  561. throw new WebApiException(2, "No user session.");
  562. }
  563. if (StringUtil.isBlank(queryId)) {
  564. throw new WebApiException(3, "Query ID is null.");
  565. }
  566. final String[] docIds = userInfoHelper.getResultDocIds(queryId);
  567. final List<Map<String, Object>> docList = searchHelper.getDocumentListByDocIds(docIds, new String[] {
  568. fessConfig.getIndexFieldUrl(), fessConfig.getIndexFieldDocId(), fessConfig.getIndexFieldFavoriteCount() },
  569. OptionalThing.empty(), SearchRequestType.JSON);
  570. List<String> urlList = new ArrayList<>(docList.size());
  571. for (final Map<String, Object> doc : docList) {
  572. final String urlObj = DocumentUtil.getValue(doc, fessConfig.getIndexFieldUrl(), String.class);
  573. if (urlObj != null) {
  574. urlList.add(urlObj);
  575. }
  576. }
  577. urlList = favoriteLogService.getUrlList(userCode, urlList);
  578. final List<String> docIdList = new ArrayList<>(urlList.size());
  579. for (final Map<String, Object> doc : docList) {
  580. final String urlObj = DocumentUtil.getValue(doc, fessConfig.getIndexFieldUrl(), String.class);
  581. if (urlObj != null && urlList.contains(urlObj)) {
  582. final String docIdObj = DocumentUtil.getValue(doc, fessConfig.getIndexFieldDocId(), String.class);
  583. if (docIdObj != null) {
  584. docIdList.add(docIdObj);
  585. }
  586. }
  587. }
  588. final StringBuilder buf = new StringBuilder(255); // TODO replace response stream
  589. buf.append("\"num\":").append(docIdList.size());
  590. buf.append(", \"doc_ids\":[");
  591. if (!docIdList.isEmpty()) {
  592. for (int i = 0; i < docIdList.size(); i++) {
  593. if (i > 0) {
  594. buf.append(',');
  595. }
  596. buf.append(escapeJson(docIdList.get(i)));
  597. }
  598. }
  599. buf.append(']');
  600. body = buf.toString();
  601. } catch (final Exception e) {
  602. if (e instanceof WebApiException) {
  603. status = ((WebApiException) e).getStatusCode();
  604. } else {
  605. status = 1;
  606. }
  607. err = e;
  608. if (logger.isDebugEnabled()) {
  609. logger.debug("Failed to process a favorites request.", e);
  610. }
  611. }
  612. writeJsonResponse(status, body, err);
  613. }
  614. protected static class JsonRequestParams extends SearchRequestParams {
  615. private final HttpServletRequest request;
  616. private final FessConfig fessConfig;
  617. private int startPosition = -1;
  618. private int pageSize = -1;
  619. protected JsonRequestParams(final HttpServletRequest request, final FessConfig fessConfig) {
  620. this.request = request;
  621. this.fessConfig = fessConfig;
  622. }
  623. @Override
  624. public String getTrackTotalHits() {
  625. return request.getParameter(Constants.TRACK_TOTAL_HITS);
  626. }
  627. @Override
  628. public String getQuery() {
  629. return request.getParameter("q");
  630. }
  631. @Override
  632. public String[] getExtraQueries() {
  633. return getParamValueArray(request, "ex_q");
  634. }
  635. @Override
  636. public Map<String, String[]> getFields() {
  637. final Map<String, String[]> fields = new HashMap<>();
  638. for (final Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
  639. final String key = entry.getKey();
  640. if (key.startsWith("fields.")) {
  641. final String[] value = simplifyArray(entry.getValue());
  642. fields.put(key.substring("fields.".length()), value);
  643. }
  644. }
  645. return fields;
  646. }
  647. @Override
  648. public Map<String, String[]> getConditions() {
  649. final Map<String, String[]> conditions = new HashMap<>();
  650. for (final Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
  651. final String key = entry.getKey();
  652. if (key.startsWith("as.")) {
  653. final String[] value = simplifyArray(entry.getValue());
  654. conditions.put(key.substring("as.".length()), value);
  655. }
  656. }
  657. return conditions;
  658. }
  659. @Override
  660. public String[] getLanguages() {
  661. return getParamValueArray(request, "lang");
  662. }
  663. @Override
  664. public GeoInfo getGeoInfo() {
  665. return createGeoInfo(request);
  666. }
  667. @Override
  668. public FacetInfo getFacetInfo() {
  669. return createFacetInfo(request);
  670. }
  671. @Override
  672. public String getSort() {
  673. return request.getParameter("sort");
  674. }
  675. @Override
  676. public int getStartPosition() {
  677. if (startPosition != -1) {
  678. return startPosition;
  679. }
  680. final String start = request.getParameter("start");
  681. if (StringUtil.isBlank(start)) {
  682. startPosition = fessConfig.getPagingSearchPageStartAsInteger();
  683. } else {
  684. try {
  685. startPosition = Integer.parseInt(start);
  686. } catch (final NumberFormatException e) {
  687. startPosition = fessConfig.getPagingSearchPageStartAsInteger();
  688. }
  689. }
  690. return startPosition;
  691. }
  692. @Override
  693. public int getPageSize() {
  694. if (pageSize != -1) {
  695. return pageSize;
  696. }
  697. final String num = request.getParameter("num");
  698. if (StringUtil.isBlank(num)) {
  699. pageSize = fessConfig.getPagingSearchPageSizeAsInteger();
  700. } else {
  701. try {
  702. pageSize = Integer.parseInt(num);
  703. if (pageSize > fessConfig.getPagingSearchPageMaxSizeAsInteger().intValue() || pageSize <= 0) {
  704. pageSize = fessConfig.getPagingSearchPageMaxSizeAsInteger();
  705. }
  706. } catch (final NumberFormatException e) {
  707. pageSize = fessConfig.getPagingSearchPageSizeAsInteger();
  708. }
  709. }
  710. return pageSize;
  711. }
  712. @Override
  713. public Object getAttribute(final String name) {
  714. return request.getAttribute(name);
  715. }
  716. @Override
  717. public Locale getLocale() {
  718. return Locale.ROOT;
  719. }
  720. @Override
  721. public SearchRequestType getType() {
  722. return SearchRequestType.JSON;
  723. }
  724. @Override
  725. public String getSimilarDocHash() {
  726. return request.getParameter("sdh");
  727. }
  728. @Override
  729. public HighlightInfo getHighlightInfo() {
  730. return ComponentUtil.getViewHelper().createHighlightInfo();
  731. }
  732. }
  733. @Override
  734. protected void writeHeaders(final HttpServletResponse response) {
  735. ComponentUtil.getFessConfig().getApiJsonResponseHeaderList().forEach(e -> response.setHeader(e.getFirst(), e.getSecond()));
  736. }
  737. }