1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.services.fieldquery;
20
21 import java.time.LocalDate;
22 import java.time.ZoneOffset;
23 import java.time.format.DateTimeFormatter;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.StringTokenizer;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30
31 import org.jdom2.Element;
32 import org.mycore.parsers.bool.MCRAndCondition;
33 import org.mycore.parsers.bool.MCRBooleanClauseParser;
34 import org.mycore.parsers.bool.MCRCondition;
35 import org.mycore.parsers.bool.MCRNotCondition;
36 import org.mycore.parsers.bool.MCROrCondition;
37 import org.mycore.parsers.bool.MCRParseException;
38 import org.mycore.parsers.bool.MCRSetCondition;
39
40
41
42
43
44
45 public class MCRQueryParser extends MCRBooleanClauseParser<Void> {
46
47
48
49
50
51
52
53
54 @Override
55 protected MCRCondition<Void> parseSimpleCondition(Element e) throws MCRParseException {
56 String name = e.getName();
57
58 if (!name.equals("condition")) {
59 throw new MCRParseException("Not a valid <" + name + ">");
60 }
61
62 String field = e.getAttributeValue("field");
63 String opera = e.getAttributeValue("operator");
64 String value = e.getAttributeValue("value");
65
66 return buildConditions(field, opera, value);
67 }
68
69
70
71
72
73
74
75
76
77
78
79
80 private MCRCondition<Void> buildConditions(String field, String oper, String value) {
81 if (field.contains(",")) {
82 StringTokenizer st = new StringTokenizer(field, ", ");
83 MCROrCondition<Void> oc = new MCROrCondition<>();
84 while (st.hasMoreTokens()) {
85 oc.addChild(buildConditions(st.nextToken(), oper, value));
86 }
87 return oc;
88 } else if (field.contains("-")) {
89 StringTokenizer st = new StringTokenizer(field, "- ");
90 String fieldFrom = st.nextToken();
91 String fieldTo = st.nextToken();
92 if (oper.equals("=")) {
93
94 MCRAndCondition<Void> ac = new MCRAndCondition<>();
95 ac.addChild(buildCondition(fieldFrom, "<=", value));
96 ac.addChild(buildCondition(fieldTo, ">=", value));
97 return ac;
98 } else if (oper.contains("<")) {
99 return buildCondition(fieldFrom, oper, value);
100 } else {
101
102 return buildCondition(fieldTo, oper, value);
103 }
104 } else {
105 return buildCondition(field, oper, value);
106 }
107 }
108
109
110
111
112
113
114
115
116
117
118
119
120 private MCRQueryCondition buildCondition(String field, String oper, String value) {
121 if ("TODAY".equals(value)) {
122 value = getToday();
123 }
124 return new MCRQueryCondition(field, oper, value);
125 }
126
127 private String getToday() {
128 return LocalDate.now(ZoneOffset.systemDefault())
129 .format(DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale.GERMANY));
130 }
131
132
133 private static Pattern pattern = Pattern.compile("([^ \t\r\n]+)\\s+([^ \t\r\n]+)\\s+([^ \"\t\r\n]+|\"[^\"]*\")");
134
135
136
137
138
139
140
141
142
143 @Override
144 protected MCRCondition<Void> parseSimpleCondition(String s) throws MCRParseException {
145 Matcher m = pattern.matcher(s);
146
147 if (!m.find()) {
148 return super.parseSimpleCondition(s);
149 }
150
151 String field = m.group(1);
152 String operator = m.group(2);
153 String value = m.group(3);
154
155 if (value.startsWith("\"") && value.endsWith("\"")) {
156 value = value.substring(1, value.length() - 1);
157 }
158
159 return buildConditions(field, operator, value);
160 }
161
162 @Override
163 public MCRCondition<Void> parse(Element condition) throws MCRParseException {
164 MCRCondition<Void> cond = super.parse(condition);
165 return normalizeCondition(cond);
166 }
167
168 @Override
169 public MCRCondition<Void> parse(String s) throws MCRParseException {
170 MCRCondition<Void> cond = super.parse(s);
171 return normalizeCondition(cond);
172 }
173
174
175
176
177
178
179
180
181
182
183 public static MCRCondition<Void> normalizeCondition(MCRCondition<Void> cond) {
184 if (cond == null) {
185 return null;
186 } else if (cond instanceof MCRSetCondition) {
187 MCRSetCondition<Void> sc = (MCRSetCondition<Void>) cond;
188 List<MCRCondition<Void>> children = sc.getChildren();
189 sc = sc instanceof MCRAndCondition ? new MCRAndCondition<>() : new MCROrCondition<>();
190 for (MCRCondition<Void> child : children) {
191 MCRCondition<Void> normalizedChild = normalizeCondition(child);
192 if (normalizedChild != null) {
193 if (normalizedChild instanceof MCRSetCondition
194 && sc.getOperator().equals(((MCRSetCondition) normalizedChild).getOperator())) {
195
196 sc.addAll(((MCRSetCondition<Void>) normalizedChild).getChildren());
197 } else {
198 sc.addChild(normalizedChild);
199 }
200 }
201 }
202 children = sc.getChildren();
203 if (children.size() == 0) {
204 return null;
205 } else if (children.size() == 1) {
206 return children.get(0);
207 } else {
208 return sc;
209 }
210 } else if (cond instanceof MCRNotCondition) {
211 MCRNotCondition<Void> nc = (MCRNotCondition<Void>) cond;
212 MCRCondition<Void> child = normalizeCondition(nc.getChild());
213 if (child == null) {
214 return null;
215 } else if (child instanceof MCRNotCondition) {
216 return normalizeCondition(((MCRNotCondition<Void>) child).getChild());
217 } else {
218 return new MCRNotCondition<>(child);
219 }
220 } else if (cond instanceof MCRQueryCondition) {
221 MCRQueryCondition qc = (MCRQueryCondition) cond;
222
223 if (!qc.getOperator().equals("contains")) {
224 return qc;
225 }
226
227
228 List<String> values = new ArrayList<>();
229
230 StringBuilder phrase = null;
231 StringTokenizer st = new StringTokenizer(qc.getValue(), " ");
232 while (st.hasMoreTokens()) {
233 String value = st.nextToken();
234 if (phrase != null) {
235
236 if (value.endsWith("'")) {
237
238 value = phrase + " " + value;
239 values.add(value);
240 phrase = null;
241 } else {
242
243 phrase.append(' ').append(value);
244 }
245 } else if (value.startsWith("'")) {
246
247 if (value.endsWith("'")) {
248
249 values.add(value.substring(1, value.length() - 1));
250 } else {
251 phrase = new StringBuilder(value);
252 }
253 } else if (value.startsWith("-'")) {
254
255 if (value.endsWith("'")) {
256
257 values.add("-" + value.substring(2, value.length() - 1));
258 } else {
259 phrase = new StringBuilder(value);
260 }
261 } else {
262 values.add(value);
263 }
264 }
265
266 MCRAndCondition<Void> ac = new MCRAndCondition<>();
267 for (String value : values) {
268 if (value.startsWith("'")) {
269 ac.addChild(new MCRQueryCondition(qc.getFieldName(), "phrase", value.substring(1,
270 value.length() - 1)));
271 } else if (value.startsWith("-'")) {
272 ac.addChild(new MCRNotCondition<>(
273 new MCRQueryCondition(qc.getFieldName(), "phrase", value.substring(2, value.length() - 1))));
274 } else if (value.contains("*") || value.contains("?")) {
275 ac.addChild(new MCRQueryCondition(qc.getFieldName(), "like", value));
276 } else if (value.startsWith("-")) {
277
278 MCRCondition<Void> subCond = new MCRQueryCondition(qc.getFieldName(), "contains",
279 value.substring(1));
280 ac.addChild(new MCRNotCondition<>(subCond));
281 } else {
282 ac.addChild(new MCRQueryCondition(qc.getFieldName(), "contains", value));
283 }
284 }
285
286 if (values.size() == 1) {
287 return ac.getChildren().get(0);
288 } else {
289 return ac;
290 }
291 } else {
292 return cond;
293 }
294 }
295
296
297 public static boolean validateQueryExpression(String query) {
298 try {
299 MCRCondition<Void> cond = new MCRQueryParser().parse(query);
300 return cond != null;
301 } catch (Throwable t) {
302 return false;
303 }
304 }
305 }