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.suggest;
17  
18  import static org.codelibs.core.stream.StreamUtil.stream;
19  
20  import java.io.IOException;
21  import java.util.Collections;
22  import java.util.Locale;
23  import java.util.Map;
24  
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  
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.commons.text.StringEscapeUtils;
33  import org.codelibs.core.lang.StringUtil;
34  import org.codelibs.fess.api.BaseJsonApiManager;
35  import org.codelibs.fess.app.service.SearchService;
36  import org.codelibs.fess.entity.FacetInfo;
37  import org.codelibs.fess.entity.GeoInfo;
38  import org.codelibs.fess.entity.HighlightInfo;
39  import org.codelibs.fess.entity.SearchRequestParams;
40  import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
41  import org.codelibs.fess.exception.InvalidAccessTokenException;
42  import org.codelibs.fess.helper.RoleQueryHelper;
43  import org.codelibs.fess.helper.SuggestHelper;
44  import org.codelibs.fess.mylasta.direction.FessConfig;
45  import org.codelibs.fess.suggest.entity.SuggestItem;
46  import org.codelibs.fess.suggest.request.suggest.SuggestRequestBuilder;
47  import org.codelibs.fess.suggest.request.suggest.SuggestResponse;
48  import org.codelibs.fess.util.ComponentUtil;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  
52  public class SuggestApiManager extends BaseJsonApiManager {
53      private static final Logger logger = LoggerFactory.getLogger(SuggestApiManager.class);
54  
55      public SuggestApiManager() {
56          setPathPrefix("/suggest");
57      }
58  
59      @PostConstruct
60      public void register() {
61          if (logger.isInfoEnabled()) {
62              logger.info("Load " + this.getClass().getSimpleName());
63          }
64          ComponentUtil.getWebApiManagerFactory().add(this);
65      }
66  
67      @Override
68      public boolean matches(final HttpServletRequest request) {
69          final String servletPath = request.getServletPath();
70          return servletPath.startsWith(pathPrefix);
71      }
72  
73      @Override
74      public void process(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException,
75              ServletException {
76          final FessConfig fessConfig = ComponentUtil.getFessConfig();
77          if (!fessConfig.isAcceptedSearchReferer(request.getHeader("referer"))) {
78              writeJsonResponse(99, StringUtil.EMPTY, "Referer is invalid.");
79              return;
80          }
81  
82          int status = 0;
83          String errMsg = StringUtil.EMPTY;
84          final StringBuilder buf = new StringBuilder(255); // TODO replace response stream
85          final RoleQueryHelper roleQueryHelper = ComponentUtil.getRoleQueryHelper();
86          final SearchService searchService = ComponentUtil.getComponent(SearchService.class);
87  
88          try {
89              final RequestParameter parameter = RequestParameter.parse(request);
90              final String[] langs = searchService.getLanguages(request, parameter);
91  
92              final SuggestHelper suggestHelper = ComponentUtil.getSuggestHelper();
93              final SuggestRequestBuilder builder = suggestHelper.suggester().suggest();
94              builder.setQuery(parameter.getQuery());
95              stream(parameter.getSuggestFields()).of(stream -> stream.forEach(builder::addField));
96              roleQueryHelper.build(SearchRequestType.SUGGEST).stream().forEach(builder::addRole);
97              builder.setSize(parameter.getNum());
98              stream(langs).of(stream -> stream.forEach(builder::addLang));
99  
100             stream(parameter.getTags()).of(stream -> stream.forEach(builder::addTag));
101             final String key = ComponentUtil.getVirtualHostHelper().getVirtualHostKey();
102             if (StringUtil.isNotBlank(key)) {
103                 builder.addTag(key);
104             }
105 
106             builder.addKind(SuggestItem.Kind.USER.toString());
107             if (ComponentUtil.getFessConfig().isSuggestSearchLog()) {
108                 builder.addKind(SuggestItem.Kind.QUERY.toString());
109             }
110             if (ComponentUtil.getFessConfig().isSuggestDocuments()) {
111                 builder.addKind(SuggestItem.Kind.DOCUMENT.toString());
112             }
113 
114             final SuggestResponse suggestResponse = builder.execute().getResponse();
115 
116             buf.append("\"result\":{");
117             buf.append("\"took\":\"").append(suggestResponse.getTookMs()).append('\"');
118 
119             buf.append(",\"total\":\"").append(suggestResponse.getTotal()).append('\"');
120 
121             buf.append(",\"num\":\"").append(suggestResponse.getNum()).append('\"');
122 
123             if (!suggestResponse.getItems().isEmpty()) {
124                 buf.append(",\"hits\":[");
125 
126                 boolean first = true;
127                 for (final SuggestItem item : suggestResponse.getItems()) {
128                     if (!first) {
129                         buf.append(',');
130                     }
131                     first = false;
132 
133                     buf.append("{\"text\":\"").append(StringEscapeUtils.escapeJson(item.getText())).append('\"');
134                     buf.append(",\"tags\":[");
135                     for (int i = 0; i < item.getTags().length; i++) {
136                         if (i > 0) {
137                             buf.append(',');
138                         }
139                         buf.append('\"').append(StringEscapeUtils.escapeJson(item.getTags()[i])).append('\"');
140                     }
141                     buf.append(']');
142                     buf.append('}');
143                 }
144                 buf.append(']');
145             }
146 
147             buf.append('}');
148         } catch (final Exception e) {
149             status = 1;
150             errMsg = e.getMessage();
151             if (errMsg == null) {
152                 errMsg = e.getClass().getName();
153             }
154             if (logger.isDebugEnabled()) {
155                 logger.debug("Failed to process a suggest request.", e);
156             }
157             if (e instanceof InvalidAccessTokenException) {
158                 final InvalidAccessTokenExceptioncodelibs/fess/exception/InvalidAccessTokenException.html#InvalidAccessTokenException">InvalidAccessTokenException iate = (InvalidAccessTokenException) e;
159                 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
160                 response.setHeader("WWW-Authenticate", "Bearer error=\"" + iate.getType() + "\"");
161             }
162         }
163 
164         writeJsonResponse(status, buf.toString(), errMsg);
165     }
166 
167     protected static class RequestParameter extends SearchRequestParams {
168         private final String query;
169 
170         private final String[] fields;
171 
172         private final int num;
173 
174         private final HttpServletRequest request;
175 
176         private final String[] tags;
177 
178         protected RequestParameter(final HttpServletRequest request, final String query, final String[] tags, final String[] fields,
179                 final int num) {
180             this.query = query;
181             this.tags = tags;
182             this.fields = fields;
183             this.num = num;
184             this.request = request;
185         }
186 
187         protected static RequestParameter parse(final HttpServletRequest request) {
188             final String query = request.getParameter("query");
189             final String fieldsStr = request.getParameter("fields");
190             final String[] fields;
191             if (StringUtil.isNotBlank(fieldsStr)) {
192                 fields = fieldsStr.split(",");
193             } else {
194                 fields = new String[0];
195             }
196 
197             final String numStr = request.getParameter("num");
198             final int num;
199             if (StringUtil.isNotBlank(numStr) && StringUtils.isNumeric(numStr)) {
200                 num = Integer.parseInt(numStr);
201             } else {
202                 num = 10;
203             }
204 
205             final String tagsStr = request.getParameter("tags");
206             final String[] tags;
207             if (StringUtil.isNotBlank(tagsStr)) {
208                 tags = tagsStr.split(",");
209             } else {
210                 tags = new String[0];
211             }
212 
213             return new RequestParameter(request, query, tags, fields, num);
214         }
215 
216         @Override
217         public String getQuery() {
218             return query;
219         }
220 
221         protected String[] getSuggestFields() {
222             return fields;
223         }
224 
225         protected int getNum() {
226             return num;
227         }
228 
229         @Override
230         public Map<String, String[]> getFields() {
231             return Collections.emptyMap();
232         }
233 
234         @Override
235         public Map<String, String[]> getConditions() {
236             return Collections.emptyMap();
237         }
238 
239         public String[] getTags() {
240             return tags;
241         }
242 
243         @Override
244         public String[] getLanguages() {
245             return getParamValueArray(request, "lang");
246         }
247 
248         @Override
249         public GeoInfo getGeoInfo() {
250             throw new UnsupportedOperationException();
251         }
252 
253         @Override
254         public FacetInfo getFacetInfo() {
255             throw new UnsupportedOperationException();
256         }
257 
258         @Override
259         public String getSort() {
260             throw new UnsupportedOperationException();
261         }
262 
263         @Override
264         public int getStartPosition() {
265             throw new UnsupportedOperationException();
266         }
267 
268         @Override
269         public int getPageSize() {
270             throw new UnsupportedOperationException();
271         }
272 
273         @Override
274         public String[] getExtraQueries() {
275             throw new UnsupportedOperationException();
276         }
277 
278         @Override
279         public Object getAttribute(final String name) {
280             throw new UnsupportedOperationException();
281         }
282 
283         @Override
284         public Locale getLocale() {
285             throw new UnsupportedOperationException();
286         }
287 
288         @Override
289         public SearchRequestType getType() {
290             return SearchRequestType.SUGGEST;
291         }
292 
293         @Override
294         public String getSimilarDocHash() {
295             throw new UnsupportedOperationException();
296         }
297 
298         @Override
299         public HighlightInfo getHighlightInfo() {
300             return new HighlightInfo();
301         }
302     }
303 
304     @Override
305     protected void writeHeaders(final HttpServletResponse response) {
306         ComponentUtil.getFessConfig().getApiJsonResponseHeaderList().forEach(e -> response.setHeader(e.getFirst(), e.getSecond()));
307     }
308 }