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 allRecordCountRelation = data.getAllRecordCountRelation();
232             final String allPageCount = Integer.toString(data.getAllPageCount());
233             final List<Map<String, Object>> documentItems = data.getDocumentItems();
234             final FacetResponse facetResponse = data.getFacetResponse();
235             final String queryId = data.getQueryId();
236             final String highlightParams = data.getAppendHighlightParams();
237             final boolean nextPage = data.isExistNextPage();
238             final boolean prevPage = data.isExistPrevPage();
239             final long startRecordNumber = data.getCurrentStartRecordNumber();
240             final long endRecordNumber = data.getCurrentEndRecordNumber();
241             final List<String> pageNumbers = data.getPageNumberList();
242             final boolean partial = data.isPartialResults();
243             final String searchQuery = data.getSearchQuery();
244             final long requestedTime = data.getRequestedTime();
245 
246             buf.append("\"q\":");
247             buf.append(escapeJson(query));
248             buf.append(",\"query_id\":");
249             buf.append(escapeJson(queryId));
250             buf.append(",\"exec_time\":");
251             buf.append(execTime);
252             buf.append(",\"query_time\":");
253             buf.append(queryTime);
254             buf.append(',');
255             buf.append("\"page_size\":");
256             buf.append(pageSize);
257             buf.append(',');
258             buf.append("\"page_number\":");
259             buf.append(currentPageNumber);
260             buf.append(',');
261             buf.append("\"record_count\":");
262             buf.append(allRecordCount);
263             buf.append(',');
264             buf.append("\"record_count_relation\":");
265             buf.append(escapeJson(allRecordCountRelation));
266             buf.append(',');
267             buf.append("\"page_count\":");
268             buf.append(allPageCount);
269             buf.append(",\"highlight_params\":");
270             buf.append(escapeJson(highlightParams));
271             buf.append(",\"next_page\":");
272             buf.append(escapeJson(nextPage));
273             buf.append(",\"prev_page\":");
274             buf.append(escapeJson(prevPage));
275             buf.append(",\"start_record_number\":");
276             buf.append(startRecordNumber);
277             buf.append(",\"end_record_number\":");
278             buf.append(escapeJson(endRecordNumber));
279             buf.append(",\"page_numbers\":");
280             buf.append(escapeJson(pageNumbers));
281             buf.append(",\"partial\":");
282             buf.append(escapeJson(partial));
283             buf.append(",\"search_query\":");
284             buf.append(escapeJson(searchQuery));
285             buf.append(",\"requested_time\":");
286             buf.append(requestedTime);
287             final String[] relatedQueries = relatedQueryHelper.getRelatedQueries(params.getQuery());
288             buf.append(",\"related_query\":");
289             buf.append(escapeJson(relatedQueries));
290             final String[] relatedContents = relatedContentHelper.getRelatedContents(params.getQuery());
291             buf.append(",\"related_contents\":");
292             buf.append(escapeJson(relatedContents));
293             buf.append(',');
294             buf.append("\"result\":[");
295             if (!documentItems.isEmpty()) {
296                 boolean first1 = true;
297                 for (final Map<String, Object> document : documentItems) {
298                     if (!first1) {
299                         buf.append(',');
300                     } else {
301                         first1 = false;
302                     }
303                     buf.append('{');
304                     boolean first2 = true;
305                     for (final Map.Entry<String, Object> entry : document.entrySet()) {
306                         final String name = entry.getKey();
307                         if (StringUtil.isNotBlank(name) && entry.getValue() != null
308                                 && ComponentUtil.getQueryHelper().isApiResponseField(name)) {
309                             if (!first2) {
310                                 buf.append(',');
311                             } else {
312                                 first2 = false;
313                             }
314                             buf.append(escapeJson(name));
315                             buf.append(':');
316                             buf.append(escapeJson(entry.getValue()));
317                         }
318                     }
319                     buf.append('}');
320                 }
321             }
322             buf.append(']');
323             if (facetResponse != null && facetResponse.hasFacetResponse()) {
324                 // facet field
325                 buf.append(',');
326                 buf.append("\"facet_field\":[");
327                 if (facetResponse.getFieldList() != null) {
328                     boolean first1 = true;
329                     for (final Field field : facetResponse.getFieldList()) {
330                         if (!first1) {
331                             buf.append(',');
332                         } else {
333                             first1 = false;
334                         }
335                         buf.append("{\"name\":");
336                         buf.append(escapeJson(field.getName()));
337                         buf.append(",\"result\":[");
338                         boolean first2 = true;
339                         for (final Map.Entry<String, Long> entry : field.getValueCountMap().entrySet()) {
340                             if (!first2) {
341                                 buf.append(',');
342                             } else {
343                                 first2 = 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                         buf.append(']');
352                         buf.append('}');
353                     }
354                 }
355                 buf.append(']');
356                 // facet q
357                 buf.append(',');
358                 buf.append("\"facet_query\":[");
359                 if (facetResponse.getQueryCountMap() != null) {
360                     boolean first1 = true;
361                     for (final Map.Entry<String, Long> entry : facetResponse.getQueryCountMap().entrySet()) {
362                         if (!first1) {
363                             buf.append(',');
364                         } else {
365                             first1 = false;
366                         }
367                         buf.append("{\"value\":");
368                         buf.append(escapeJson(entry.getKey()));
369                         buf.append(",\"count\":");
370                         buf.append(entry.getValue());
371                         buf.append('}');
372                     }
373                 }
374                 buf.append(']');
375             }
376         } catch (final Exception e) {
377             status = 1;
378             err = e;
379             if (logger.isDebugEnabled()) {
380                 logger.debug("Failed to process a search request.", e);
381             }
382         }
383 
384         writeJsonResponse(status, buf.toString(), err);
385 
386     }
387 
388     protected String toGeoRequestString(final GeoInfo geoInfo) {
389         try (OutputStream out = EsUtil.getXContentOutputStream(geoInfo.toQueryBuilder(), XContentType.JSON)) {
390             return ((ByteArrayOutputStream) out).toString(Constants.UTF_8);
391         } catch (final Exception e) {
392             return "{\"error\":\"" + detailedMessage(e) + "\"}";
393         }
394     }
395 
396     protected String detailedMessage(final Throwable t) {
397         if (t == null) {
398             return "Unknown";
399         }
400         Throwable target = t;
401         if (target.getCause() != null) {
402             final StringBuilder sb = new StringBuilder();
403             while (target != null) {
404                 sb.append(target.getClass().getSimpleName());
405                 if (target.getMessage() != null) {
406                     sb.append("[");
407                     sb.append(target.getMessage());
408                     sb.append("]");
409                 }
410                 sb.append("; ");
411                 target = target.getCause();
412                 if (target != null) {
413                     sb.append("nested: ");
414                 }
415             }
416             return sb.toString();
417         } else {
418             return target.getClass().getSimpleName() + "[" + target.getMessage() + "]";
419         }
420     }
421 
422     protected void processLabelRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) {
423         final LabelTypeHelper labelTypeHelper = ComponentUtil.getLabelTypeHelper();
424 
425         int status = 0;
426         Exception err = null;
427         final StringBuilder buf = new StringBuilder(255); // TODO replace response stream
428         try {
429             final List<Map<String, String>> labelTypeItems = labelTypeHelper.getLabelTypeItemList(SearchRequestType.JSON);
430             buf.append("\"record_count\":");
431             buf.append(labelTypeItems.size());
432             if (!labelTypeItems.isEmpty()) {
433                 buf.append(',');
434                 buf.append("\"result\":[");
435                 boolean first1 = true;
436                 for (final Map<String, String> labelMap : labelTypeItems) {
437                     if (!first1) {
438                         buf.append(',');
439                     } else {
440                         first1 = false;
441                     }
442                     buf.append("{\"label\":");
443                     buf.append(escapeJson(labelMap.get(Constants.ITEM_LABEL)));
444                     buf.append(", \"value\":");
445                     buf.append(escapeJson(labelMap.get(Constants.ITEM_VALUE)));
446                     buf.append('}');
447                 }
448                 buf.append(']');
449             }
450         } catch (final Exception e) {
451             status = 1;
452             err = e;
453             if (logger.isDebugEnabled()) {
454                 logger.debug("Failed to process a label request.", e);
455             }
456         }
457 
458         writeJsonResponse(status, buf.toString(), err);
459 
460     }
461 
462     protected void processPopularWordRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) {
463         if (!ComponentUtil.getFessConfig().isWebApiPopularWord()) {
464             writeJsonResponse(9, null, "Unsupported operation.");
465             return;
466         }
467 
468         final String seed = request.getParameter("seed");
469         final List<String> tagList = new ArrayList<>();
470         final String[] tags = request.getParameterValues("labels");
471         if (tags != null) {
472             tagList.addAll(Arrays.asList(tags));
473         }
474         final String key = ComponentUtil.getVirtualHostHelper().getVirtualHostKey();
475         if (StringUtil.isNotBlank(key)) {
476             tagList.add(key);
477         }
478         final String[] fields = request.getParameterValues("fields");
479         final String[] excludes = StringUtil.EMPTY_STRINGS;// TODO
480 
481         final PopularWordHelper popularWordHelper = ComponentUtil.getPopularWordHelper();
482 
483         int status = 0;
484         Exception err = null;
485         final StringBuilder buf = new StringBuilder(255); // TODO replace response stream
486         try {
487             final List<String> popularWordList =
488                     popularWordHelper.getWordList(SearchRequestType.JSON, seed, tagList.toArray(new String[tagList.size()]), null, fields,
489                             excludes);
490 
491             buf.append("\"result\":[");
492             boolean first1 = true;
493             for (final String word : popularWordList) {
494                 if (!first1) {
495                     buf.append(',');
496                 } else {
497                     first1 = false;
498                 }
499                 buf.append(escapeJson(word));
500             }
501             buf.append(']');
502         } catch (final Exception e) {
503             if (e instanceof WebApiException) {
504                 status = ((WebApiException) e).getStatusCode();
505             } else {
506                 status = 1;
507             }
508             err = e;
509             if (logger.isDebugEnabled()) {
510                 logger.debug("Failed to process a popularWord request.", e);
511             }
512         }
513 
514         writeJsonResponse(status, buf.toString(), err);
515 
516     }
517 
518     protected void processFavoriteRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) {
519         if (!ComponentUtil.getFessConfig().isUserFavorite()) {
520             writeJsonResponse(9, null, "Unsupported operation.");
521             return;
522         }
523 
524         final FessConfig fessConfig = ComponentUtil.getFessConfig();
525         final UserInfoHelper userInfoHelper = ComponentUtil.getUserInfoHelper();
526         final SearchService searchService = ComponentUtil.getComponent(SearchService.class);
527         final FavoriteLogService favoriteLogService = ComponentUtil.getComponent(FavoriteLogService.class);
528         final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
529 
530         try {
531             final String docId = request.getParameter("docId");
532             final String queryId = request.getParameter("queryId");
533 
534             final String[] docIds = userInfoHelper.getResultDocIds(URLDecoder.decode(queryId, Constants.UTF_8));
535             if (docIds == null) {
536                 throw new WebApiException(6, "No searched urls.");
537             }
538 
539             searchService.getDocumentByDocId(docId, new String[] { fessConfig.getIndexFieldUrl() }, OptionalThing.empty())
540                     .ifPresent(doc -> {
541                         final String favoriteUrl = DocumentUtil.getValue(doc, fessConfig.getIndexFieldUrl(), String.class);
542                         final String userCode = userInfoHelper.getUserCode();
543 
544                         if (StringUtil.isBlank(userCode)) {
545                             throw new WebApiException(2, "No user session.");
546                         } else if (StringUtil.isBlank(favoriteUrl)) {
547                             throw new WebApiException(2, "URL is null.");
548                         }
549 
550                         boolean found = false;
551                         for (final String id : docIds) {
552                             if (docId.equals(id)) {
553                                 found = true;
554                                 break;
555                             }
556                         }
557                         if (!found) {
558                             throw new WebApiException(5, "Not found: " + favoriteUrl);
559                         }
560 
561                         if (!favoriteLogService.addUrl(userCode, (userInfo, favoriteLog) -> {
562                             favoriteLog.setUserInfoId(userInfo.getId());
563                             favoriteLog.setUrl(favoriteUrl);
564                             favoriteLog.setDocId(docId);
565                             favoriteLog.setQueryId(queryId);
566                             favoriteLog.setCreatedAt(systemHelper.getCurrentTimeAsLocalDateTime());
567                         })) {
568                             throw new WebApiException(4, "Failed to add url: " + favoriteUrl);
569                         }
570 
571                         final String id = DocumentUtil.getValue(doc, fessConfig.getIndexFieldId(), String.class);
572                         searchService.update(id, builder -> {
573                             final Script script = new Script("ctx._source." + fessConfig.getIndexFieldFavoriteCount() + "+=1");
574                             builder.setScript(script);
575                             final Map<String, Object> upsertMap = new HashMap<>();
576                             upsertMap.put(fessConfig.getIndexFieldFavoriteCount(), 1);
577                             builder.setUpsert(upsertMap);
578                             builder.setRefreshPolicy(Constants.TRUE);
579                         });
580 
581                         writeJsonResponse(0, "\"result\":\"ok\"", (String) null);
582 
583                     }).orElse(() -> {
584                         throw new WebApiException(6, "Not found: " + docId);
585                     });
586 
587         } catch (final Exception e) {
588             int status;
589             if (e instanceof WebApiException) {
590                 status = ((WebApiException) e).getStatusCode();
591             } else {
592                 status = 1;
593             }
594             writeJsonResponse(status, null, e);
595             if (logger.isDebugEnabled()) {
596                 logger.debug("Failed to process a favorite request.", e);
597             }
598         }
599 
600     }
601 
602     protected void processFavoritesRequest(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) {
603         if (!ComponentUtil.getFessConfig().isUserFavorite()) {
604             writeJsonResponse(9, null, "Unsupported operation.");
605             return;
606         }
607 
608         final UserInfoHelper userInfoHelper = ComponentUtil.getUserInfoHelper();
609         final FessConfig fessConfig = ComponentUtil.getFessConfig();
610         final SearchService searchService = ComponentUtil.getComponent(SearchService.class);
611         final FavoriteLogService favoriteLogService = ComponentUtil.getComponent(FavoriteLogService.class);
612 
613         int status = 0;
614         String body = null;
615         Exception err = null;
616 
617         try {
618             final String queryId = request.getParameter("queryId");
619             final String userCode = userInfoHelper.getUserCode();
620 
621             if (StringUtil.isBlank(userCode)) {
622                 throw new WebApiException(2, "No user session.");
623             } else if (StringUtil.isBlank(queryId)) {
624                 throw new WebApiException(3, "Query ID is null.");
625             }
626 
627             final String[] docIds = userInfoHelper.getResultDocIds(queryId);
628             final List<Map<String, Object>> docList =
629                     searchService.getDocumentListByDocIds(
630                             docIds,
631                             new String[] { fessConfig.getIndexFieldUrl(), fessConfig.getIndexFieldDocId(),
632                                     fessConfig.getIndexFieldFavoriteCount() }, OptionalThing.empty(), SearchRequestType.JSON);
633             List<String> urlList = new ArrayList<>(docList.size());
634             for (final Map<String, Object> doc : docList) {
635                 final String urlObj = DocumentUtil.getValue(doc, fessConfig.getIndexFieldUrl(), String.class);
636                 if (urlObj != null) {
637                     urlList.add(urlObj);
638                 }
639             }
640             urlList = favoriteLogService.getUrlList(userCode, urlList);
641             final List<String> docIdList = new ArrayList<>(urlList.size());
642             for (final Map<String, Object> doc : docList) {
643                 final String urlObj = DocumentUtil.getValue(doc, fessConfig.getIndexFieldUrl(), String.class);
644                 if (urlObj != null && urlList.contains(urlObj)) {
645                     final String docIdObj = DocumentUtil.getValue(doc, fessConfig.getIndexFieldDocId(), String.class);
646                     if (docIdObj != null) {
647                         docIdList.add(docIdObj);
648                     }
649                 }
650             }
651 
652             final StringBuilder buf = new StringBuilder(255); // TODO replace response stream
653             buf.append("\"num\":").append(docIdList.size());
654             buf.append(", \"doc_ids\":[");
655             if (!docIdList.isEmpty()) {
656                 for (int i = 0; i < docIdList.size(); i++) {
657                     if (i > 0) {
658                         buf.append(',');
659                     }
660                     buf.append(escapeJson(docIdList.get(i)));
661                 }
662             }
663             buf.append(']');
664             body = buf.toString();
665         } catch (final Exception e) {
666             if (e instanceof WebApiException) {
667                 status = ((WebApiException) e).getStatusCode();
668             } else {
669                 status = 1;
670             }
671 
672             err = e;
673             if (logger.isDebugEnabled()) {
674                 logger.debug("Failed to process a favorites request.", e);
675             }
676         }
677 
678         writeJsonResponse(status, body, err);
679 
680     }
681 
682     protected static class JsonRequestParams extends SearchRequestParams {
683 
684         private final HttpServletRequest request;
685 
686         private final FessConfig fessConfig;
687 
688         private int startPosition = -1;
689 
690         private int pageSize = -1;
691 
692         protected JsonRequestParams(final HttpServletRequest request, final FessConfig fessConfig) {
693             this.request = request;
694             this.fessConfig = fessConfig;
695         }
696 
697         @Override
698         public String getQuery() {
699             return request.getParameter("q");
700         }
701 
702         @Override
703         public String[] getExtraQueries() {
704             return getParamValueArray(request, "ex_q");
705         }
706 
707         @Override
708         public Map<String, String[]> getFields() {
709             final Map<String, String[]> fields = new HashMap<>();
710             for (final Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
711                 final String key = entry.getKey();
712                 if (key.startsWith("fields.")) {
713                     final String[] value = simplifyArray(entry.getValue());
714                     fields.put(key.substring("fields.".length()), value);
715                 }
716             }
717             return fields;
718         }
719 
720         @Override
721         public Map<String, String[]> getConditions() {
722             final Map<String, String[]> conditions = new HashMap<>();
723             for (final Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
724                 final String key = entry.getKey();
725                 if (key.startsWith("as.")) {
726                     final String[] value = simplifyArray(entry.getValue());
727                     conditions.put(key.substring("as.".length()), value);
728                 }
729             }
730             return conditions;
731         }
732 
733         @Override
734         public String[] getLanguages() {
735             return getParamValueArray(request, "lang");
736         }
737 
738         @Override
739         public GeoInfo getGeoInfo() {
740             return createGeoInfo(request);
741         }
742 
743         @Override
744         public FacetInfo getFacetInfo() {
745             return createFacetInfo(request);
746         }
747 
748         @Override
749         public String getSort() {
750             return request.getParameter("sort");
751         }
752 
753         @Override
754         public int getStartPosition() {
755             if (startPosition != -1) {
756                 return startPosition;
757             }
758 
759             final String start = request.getParameter("start");
760             if (StringUtil.isBlank(start)) {
761                 startPosition = fessConfig.getPagingSearchPageStartAsInteger();
762             } else {
763                 try {
764                     startPosition = Integer.parseInt(start);
765                 } catch (final NumberFormatException e) {
766                     startPosition = fessConfig.getPagingSearchPageStartAsInteger();
767                 }
768             }
769             return startPosition;
770         }
771 
772         @Override
773         public int getPageSize() {
774             if (pageSize != -1) {
775                 return pageSize;
776             }
777 
778             final String num = request.getParameter("num");
779             if (StringUtil.isBlank(num)) {
780                 pageSize = fessConfig.getPagingSearchPageSizeAsInteger();
781             } else {
782                 try {
783                     pageSize = Integer.parseInt(num);
784                     if (pageSize > fessConfig.getPagingSearchPageMaxSizeAsInteger().intValue() || pageSize <= 0) {
785                         pageSize = fessConfig.getPagingSearchPageMaxSizeAsInteger();
786                     }
787                 } catch (final NumberFormatException e) {
788                     pageSize = fessConfig.getPagingSearchPageSizeAsInteger();
789                 }
790             }
791             return pageSize;
792         }
793 
794         @Override
795         public Object getAttribute(final String name) {
796             return request.getAttribute(name);
797         }
798 
799         @Override
800         public Locale getLocale() {
801             return Locale.ROOT;
802         }
803 
804         @Override
805         public SearchRequestType getType() {
806             return SearchRequestType.JSON;
807         }
808 
809         @Override
810         public String getSimilarDocHash() {
811             return request.getParameter("sdh");
812         }
813 
814         @Override
815         public HighlightInfo getHighlightInfo() {
816             return ComponentUtil.getViewHelper().createHighlightInfo();
817         }
818     }
819 
820     @Override
821     protected void writeHeaders(final HttpServletResponse response) {
822         ComponentUtil.getFessConfig().getApiJsonResponseHeaderList().forEach(e -> response.setHeader(e.getFirst(), e.getSecond()));
823     }
824 }