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