1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.util;
17
18 import static org.codelibs.core.stream.StreamUtil.split;
19 import static org.codelibs.core.stream.StreamUtil.stream;
20
21 import java.util.Map;
22 import java.util.stream.Collectors;
23
24 import org.codelibs.core.lang.StringUtil;
25 import org.codelibs.fess.Constants;
26 import org.codelibs.fess.entity.SearchRequestParams;
27 import org.codelibs.fess.helper.RelatedQueryHelper;
28 import org.codelibs.fess.mylasta.direction.FessConfig;
29
30
31
32
33
34
35 public class QueryStringBuilder {
36
37 private static final String OR_ALT = " || ";
38
39 private static final String OR = " OR ";
40
41 private static final String SPACE = " ";
42
43 private SearchRequestParams params;
44
45 private boolean escape = false;
46
47 private String sortField;
48
49
50
51
52
53 public QueryStringBuilder() {
54
55 }
56
57
58
59
60
61
62
63
64 protected String quote(final String value) {
65 if (value.split("\\s").length > 1) {
66 return new StringBuilder().append('"').append(value.replace('"', ' ')).append('"').toString();
67 }
68 return value;
69 }
70
71
72
73
74
75
76
77
78 protected String escapeQuery(final String value) {
79 if (!escape) {
80 return value;
81 }
82
83 String newValue = value;
84 for (final String element : Constants.RESERVED) {
85 final String replacement = element.replaceAll("(.)", "\\\\$1");
86 newValue = newValue.replace(element, replacement);
87 }
88 return newValue;
89 }
90
91
92
93
94
95
96
97 public String build() {
98 final FessConfig fessConfig = ComponentUtil.getFessConfig();
99 final int maxQueryLength = fessConfig.getQueryMaxLengthAsInteger();
100 final StringBuilder queryBuf = new StringBuilder(255);
101
102 final String query = buildBaseQuery();
103 if (StringUtil.isNotBlank(query)) {
104 queryBuf.append(escapeQuery(query));
105 }
106
107 stream(params.getExtraQueries())
108 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength).forEach(q -> {
109 appendQuery(queryBuf, q);
110 }));
111
112 stream(params.getFields()).of(stream -> stream.forEach(entry -> {
113 final String key = entry.getKey();
114 final String[] values = entry.getValue();
115 if (values == null) {
116
117 } else if (values.length == 1) {
118 queryBuf.append(' ').append(key).append(":\"").append(values[0]).append('\"');
119 } else if (values.length > 1) {
120 boolean first = true;
121 queryBuf.append(" (");
122 for (final String value : values) {
123 if (first) {
124 first = false;
125 } else {
126 queryBuf.append(OR);
127 }
128 queryBuf.append(key).append(":\"").append(value).append('\"');
129 }
130 queryBuf.append(')');
131 }
132 }));
133
134 final String baseQuery = queryBuf.toString().trim();
135 if (StringUtil.isBlank(sortField)) {
136 return baseQuery;
137 }
138 return baseQuery + " sort:" + sortField;
139 }
140
141
142
143
144
145
146
147
148 protected void appendQuery(final StringBuilder queryBuf, final String query) {
149 String q = query;
150 for (final String s : ComponentUtil.getFessConfig().getCrawlerDocumentSpaces()) {
151 q = q.replace(s, SPACE);
152 }
153 final boolean exists = q.indexOf(OR) != -1 || q.indexOf(OR_ALT) != -1;
154 queryBuf.append(' ');
155 if (exists) {
156 queryBuf.append('(');
157 }
158 queryBuf.append(query);
159 if (exists) {
160 queryBuf.append(')');
161 }
162 }
163
164
165
166
167
168
169
170 protected String buildBaseQuery() {
171 final StringBuilder queryBuf = new StringBuilder(255);
172 if (params.hasConditionQuery()) {
173 appendConditions(queryBuf, params.getConditions());
174 } else {
175 final String query = params.getQuery();
176 if (StringUtil.isNotBlank(query)) {
177 if (ComponentUtil.hasRelatedQueryHelper()) {
178 final RelatedQueryHelper relatedQueryHelper = ComponentUtil.getRelatedQueryHelper();
179 final String[] relatedQueries = relatedQueryHelper.getRelatedQueries(query);
180 if (relatedQueries.length == 0) {
181 appendQuery(queryBuf, query);
182 } else {
183 queryBuf.append('(');
184 queryBuf.append(quote(query));
185 for (final String s : relatedQueries) {
186 queryBuf.append(OR);
187 queryBuf.append(quote(s));
188 }
189 queryBuf.append(')');
190 }
191 } else {
192 appendQuery(queryBuf, query);
193 }
194 }
195 }
196 return queryBuf.toString().trim();
197 }
198
199
200
201
202
203
204
205
206
207 protected void appendConditions(final StringBuilder queryBuf, final Map<String, String[]> conditions) {
208 if (conditions == null) {
209 return;
210 }
211 final FessConfig fessConfig = ComponentUtil.getFessConfig();
212 final int maxQueryLength = fessConfig.getQueryMaxLengthAsInteger();
213
214 stream(conditions.get(SearchRequestParams.AS_OCCURRENCE))
215 .of(stream -> stream.filter(this::isOccurrence).findFirst().ifPresent(q -> queryBuf.insert(0, q + ":")));
216
217 stream(conditions.get(SearchRequestParams.AS_Q))
218 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength)
219 .forEach(q -> queryBuf.append(' ').append(q)));
220 stream(conditions.get(SearchRequestParams.AS_EPQ))
221 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength)
222 .forEach(q -> queryBuf.append(" \"").append(escape(q, "\"")).append('"')));
223 stream(conditions.get(SearchRequestParams.AS_OQ)).of(stream -> stream
224 .filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength)
225 .forEach(oq -> split(oq, " ")
226 .get(s -> s.filter(StringUtil::isNotBlank).reduce((q1, q2) -> escape(q1, "(", ")") + OR + escape(q2, "(", ")")))
227 .ifPresent(q -> {
228 appendQuery(queryBuf, q);
229 })));
230 stream(conditions.get(SearchRequestParams.AS_NQ))
231 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength).forEach(eq -> {
232 final String nq =
233 split(eq, " ").get(s -> s.filter(StringUtil::isNotBlank).map(q -> "NOT " + q).collect(Collectors.joining(" ")));
234 queryBuf.append(' ').append(nq);
235 }));
236 stream(conditions.get(SearchRequestParams.AS_FILETYPE))
237 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength)
238 .forEach(q -> queryBuf.append(" filetype:\"").append(q.trim()).append('"')));
239 stream(conditions.get(SearchRequestParams.AS_SITESEARCH))
240 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength)
241 .forEach(q -> queryBuf.append(" site:").append(q.trim())));
242 stream(conditions.get(SearchRequestParams.AS_TIMESTAMP))
243 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength)
244 .forEach(q -> queryBuf.append(" timestamp:").append(q.trim())));
245 }
246
247
248
249
250
251
252
253
254 protected boolean isOccurrence(final String value) {
255 return "allintitle".equals(value) || "allinurl".equals(value);
256 }
257
258
259
260
261
262
263
264
265
266 protected String escape(final String q, final String... values) {
267 String value = q;
268 for (final String s : values) {
269 value = value.replace(s, "\\" + s);
270 }
271 return value;
272 }
273
274
275
276
277
278
279
280
281 public QueryStringBuilder params(final SearchRequestParams params) {
282 this.params = params;
283 return this;
284 }
285
286
287
288
289
290
291
292
293 public QueryStringBuilder sortField(final String sortField) {
294 this.sortField = sortField;
295 return this;
296 }
297
298
299
300
301
302
303
304
305 public QueryStringBuilder escape(final boolean escape) {
306 this.escape = escape;
307 return this;
308 }
309 }