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 public class QueryStringBuilder {
31
32 private static final String OR_ALT = " || ";
33
34 private static final String OR = " OR ";
35
36 private static final String SPACE = " ";
37
38 private SearchRequestParams params;
39
40 private boolean escape = false;
41
42 private String sortField;
43
44 protected String quote(final String value) {
45 if (value.split("\\s").length > 1) {
46 return new StringBuilder().append('"').append(value.replace('"', ' ')).append('"').toString();
47 }
48 return value;
49 }
50
51 protected String escapeQuery(final String value) {
52 if (!escape) {
53 return value;
54 }
55
56 String newValue = value;
57 for (final String element : Constants.RESERVED) {
58 final String replacement = element.replaceAll("(.)", "\\\\$1");
59 newValue = newValue.replace(element, replacement);
60 }
61 return newValue;
62 }
63
64 public String build() {
65 final FessConfig fessConfig = ComponentUtil.getFessConfig();
66 final int maxQueryLength = fessConfig.getQueryMaxLengthAsInteger();
67 final StringBuilder queryBuf = new StringBuilder(255);
68
69 final String query = buildBaseQuery();
70 if (StringUtil.isNotBlank(query)) {
71 queryBuf.append(escapeQuery(query));
72 }
73
74 stream(params.getExtraQueries())
75 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength).forEach(q -> {
76 appendQuery(queryBuf, q);
77 }));
78
79 stream(params.getFields()).of(stream -> stream.forEach(entry -> {
80 final String key = entry.getKey();
81 final String[] values = entry.getValue();
82 if (values == null) {
83
84 } else if (values.length == 1) {
85 queryBuf.append(' ').append(key).append(":\"").append(values[0]).append('\"');
86 } else if (values.length > 1) {
87 boolean first = true;
88 queryBuf.append(" (");
89 for (final String value : values) {
90 if (first) {
91 first = false;
92 } else {
93 queryBuf.append(OR);
94 }
95 queryBuf.append(key).append(":\"").append(value).append('\"');
96 }
97 queryBuf.append(')');
98 }
99 }));
100
101 final String baseQuery = queryBuf.toString().trim();
102 if (StringUtil.isBlank(sortField)) {
103 return baseQuery;
104 }
105 return baseQuery + " sort:" + sortField;
106 }
107
108 protected void appendQuery(final StringBuilder queryBuf, final String query) {
109 String q = query;
110 for (final String s : ComponentUtil.getFessConfig().getCrawlerDocumentSpaces()) {
111 q = q.replace(s, SPACE);
112 }
113 final boolean exists = q.indexOf(OR) != -1 || q.indexOf(OR_ALT) != -1;
114 queryBuf.append(' ');
115 if (exists) {
116 queryBuf.append('(');
117 }
118 queryBuf.append(query);
119 if (exists) {
120 queryBuf.append(')');
121 }
122 }
123
124 protected String buildBaseQuery() {
125 final StringBuilder queryBuf = new StringBuilder(255);
126 if (params.hasConditionQuery()) {
127 appendConditions(queryBuf, params.getConditions());
128 } else {
129 final String query = params.getQuery();
130 if (StringUtil.isNotBlank(query)) {
131 if (ComponentUtil.hasRelatedQueryHelper()) {
132 final RelatedQueryHelper relatedQueryHelper = ComponentUtil.getRelatedQueryHelper();
133 final String[] relatedQueries = relatedQueryHelper.getRelatedQueries(query);
134 if (relatedQueries.length == 0) {
135 appendQuery(queryBuf, query);
136 } else {
137 queryBuf.append('(');
138 queryBuf.append(quote(query));
139 for (final String s : relatedQueries) {
140 queryBuf.append(OR);
141 queryBuf.append(quote(s));
142 }
143 queryBuf.append(')');
144 }
145 } else {
146 appendQuery(queryBuf, query);
147 }
148 }
149 }
150 return queryBuf.toString().trim();
151 }
152
153 protected void appendConditions(final StringBuilder queryBuf, final Map<String, String[]> conditions) {
154 if (conditions == null) {
155 return;
156 }
157 final FessConfig fessConfig = ComponentUtil.getFessConfig();
158 final int maxQueryLength = fessConfig.getQueryMaxLengthAsInteger();
159
160 stream(conditions.get(SearchRequestParams.AS_OCCURRENCE))
161 .of(stream -> stream.filter(this::isOccurrence).findFirst().ifPresent(q -> queryBuf.insert(0, q + ":")));
162
163 stream(conditions.get(SearchRequestParams.AS_Q)).of(stream -> stream
164 .filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength).forEach(q -> queryBuf.append(' ').append(q)));
165 stream(conditions.get(SearchRequestParams.AS_EPQ))
166 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength)
167 .forEach(q -> queryBuf.append(" \"").append(escape(q, "\"")).append('"')));
168 stream(conditions.get(SearchRequestParams.AS_OQ)).of(stream -> stream
169 .filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength)
170 .forEach(oq -> split(oq, " ")
171 .get(s -> s.filter(StringUtil::isNotBlank).reduce((q1, q2) -> escape(q1, "(", ")") + OR + escape(q2, "(", ")")))
172 .ifPresent(q -> {
173 appendQuery(queryBuf, q);
174 })));
175 stream(conditions.get(SearchRequestParams.AS_NQ))
176 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength).forEach(eq -> {
177 final String nq =
178 split(eq, " ").get(s -> s.filter(StringUtil::isNotBlank).map(q -> "NOT " + q).collect(Collectors.joining(" ")));
179 queryBuf.append(' ').append(nq);
180 }));
181 stream(conditions.get(SearchRequestParams.AS_FILETYPE))
182 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength)
183 .forEach(q -> queryBuf.append(" filetype:\"").append(q.trim()).append('"')));
184 stream(conditions.get(SearchRequestParams.AS_SITESEARCH))
185 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength)
186 .forEach(q -> queryBuf.append(" site:").append(q.trim())));
187 stream(conditions.get(SearchRequestParams.AS_TIMESTAMP))
188 .of(stream -> stream.filter(q -> StringUtil.isNotBlank(q) && q.length() <= maxQueryLength)
189 .forEach(q -> queryBuf.append(" timestamp:").append(q.trim())));
190 }
191
192 protected boolean isOccurrence(final String value) {
193 return "allintitle".equals(value) || "allinurl".equals(value);
194 }
195
196 protected String escape(final String q, final String... values) {
197 String value = q;
198 for (final String s : values) {
199 value = value.replace(s, "\\" + s);
200 }
201 return value;
202 }
203
204 public QueryStringBuilder params(final SearchRequestParams params) {
205 this.params = params;
206 return this;
207 }
208
209 public QueryStringBuilder sortField(final String sortField) {
210 this.sortField = sortField;
211 return this;
212 }
213
214 public QueryStringBuilder escape(final boolean escape) {
215 this.escape = escape;
216 return this;
217 }
218 }