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.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.apache.logging.log4j.LogManager;
34 import org.apache.logging.log4j.Logger;
35 import org.codelibs.core.lang.StringUtil;
36 import org.codelibs.fess.api.BaseJsonApiManager;
37 import org.codelibs.fess.entity.FacetInfo;
38 import org.codelibs.fess.entity.GeoInfo;
39 import org.codelibs.fess.entity.HighlightInfo;
40 import org.codelibs.fess.entity.SearchRequestParams;
41 import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
42 import org.codelibs.fess.exception.InvalidAccessTokenException;
43 import org.codelibs.fess.helper.RoleQueryHelper;
44 import org.codelibs.fess.helper.SearchHelper;
45 import org.codelibs.fess.helper.SuggestHelper;
46 import org.codelibs.fess.mylasta.direction.FessConfig;
47 import org.codelibs.fess.suggest.entity.SuggestItem;
48 import org.codelibs.fess.suggest.request.suggest.SuggestRequestBuilder;
49 import org.codelibs.fess.suggest.request.suggest.SuggestResponse;
50 import org.codelibs.fess.util.ComponentUtil;
51
52 public class SuggestApiManager extends BaseJsonApiManager {
53 private static final Logger logger = LogManager.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)
75 throws IOException, 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);
85 final RoleQueryHelper roleQueryHelper = ComponentUtil.getRoleQueryHelper();
86 final SearchHelper searchHelper = ComponentUtil.getSearchHelper();
87
88 try {
89 final RequestParameter parameter = RequestParameter.parse(request);
90 final String[] langs = searchHelper.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 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 }