1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
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 }