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