1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.solr.search;
20
21 import static org.mycore.solr.MCRSolrConstants.SOLR_CONFIG_PREFIX;
22
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.stream.Collectors;
32
33 import org.apache.logging.log4j.LogManager;
34 import org.apache.logging.log4j.Logger;
35 import org.apache.solr.client.solrj.SolrQuery;
36 import org.apache.solr.client.solrj.SolrQuery.ORDER;
37 import org.apache.solr.client.solrj.SolrQuery.SortClause;
38 import org.mycore.common.MCRException;
39 import org.mycore.common.config.MCRConfiguration2;
40 import org.mycore.parsers.bool.MCRAndCondition;
41 import org.mycore.parsers.bool.MCRCondition;
42 import org.mycore.parsers.bool.MCRNotCondition;
43 import org.mycore.parsers.bool.MCROrCondition;
44 import org.mycore.parsers.bool.MCRSetCondition;
45 import org.mycore.services.fieldquery.MCRQueryCondition;
46 import org.mycore.services.fieldquery.MCRSortBy;
47 import org.mycore.solr.MCRSolrConstants;
48 import org.mycore.solr.MCRSolrUtils;
49
50
51
52
53
54
55 public class MCRConditionTransformer {
56 private static final Logger LOGGER = LogManager.getLogger(MCRConditionTransformer.class);
57
58
59
60
61
62 protected static final String MIXED = "--mixed--";
63
64 private static HashSet<String> joinFields = null;
65
66 public static String toSolrQueryString(@SuppressWarnings("rawtypes") MCRCondition condition,
67 Set<String> usedFields) {
68 return toSolrQueryString(condition, usedFields, false).toString();
69 }
70
71 public static boolean explicitAndOrMapping() {
72 return MCRConfiguration2.getBoolean("MCR.Solr.ConditionTransformer.ExplicitAndOrMapping").orElse(false);
73 }
74
75 @SuppressWarnings({ "unchecked", "rawtypes" })
76 private static StringBuilder toSolrQueryString(MCRCondition condition, Set<String> usedFields,
77 boolean subCondition) {
78 if (condition instanceof MCRQueryCondition) {
79 MCRQueryCondition qCond = (MCRQueryCondition) condition;
80 return handleQueryCondition(qCond, usedFields);
81 }
82 if (condition instanceof MCRSetCondition) {
83 MCRSetCondition<MCRCondition> setCond = (MCRSetCondition<MCRCondition>) condition;
84 return handleSetCondition(setCond, usedFields, subCondition);
85 }
86 if (condition instanceof MCRNotCondition) {
87 MCRNotCondition notCond = (MCRNotCondition) condition;
88 return handleNotCondition(notCond, usedFields);
89 }
90 throw new MCRException("Cannot handle MCRCondition class: " + condition.getClass().getCanonicalName());
91 }
92
93 private static StringBuilder handleQueryCondition(MCRQueryCondition qCond, Set<String> usedFields) {
94 String field = qCond.getFieldName();
95 String value = qCond.getValue();
96 String operator = qCond.getOperator();
97 usedFields.add(field);
98 switch (operator) {
99 case "like":
100 case "contains":
101 return getTermQuery(field, value.trim());
102 case "=":
103 case "phrase":
104 return getPhraseQuery(field, value);
105 case "<":
106 return getLTQuery(field, value);
107 case "<=":
108 return getLTEQuery(field, value);
109 case ">":
110 return getGTQuery(field, value);
111 case ">=":
112 return getGTEQuery(field, value);
113 }
114 throw new UnsupportedOperationException("Do not know how to handle operator: " + operator);
115 }
116
117 @SuppressWarnings("rawtypes")
118 private static StringBuilder handleSetCondition(MCRSetCondition<MCRCondition> setCond, Set<String> usedFields,
119 boolean subCondition) {
120 if (explicitAndOrMapping()) {
121 return handleSetConditionExplicit(setCond, usedFields);
122 } else {
123 return handleSetConditionDefault(setCond, usedFields, subCondition);
124 }
125 }
126
127 @SuppressWarnings("rawtypes")
128 private static StringBuilder handleSetConditionExplicit(MCRSetCondition<MCRCondition> setCond,
129 Set<String> usedFields) {
130 List<MCRCondition<MCRCondition>> children = setCond.getChildren();
131 if (children.isEmpty()) {
132 return null;
133 }
134 StringBuilder sb = new StringBuilder();
135 sb.append("(");
136 Iterator<MCRCondition<MCRCondition>> iterator = children.iterator();
137 StringBuilder subSb = toSolrQueryString(iterator.next(), usedFields, true);
138 sb.append(subSb);
139 while (iterator.hasNext()) {
140 sb.append(' ').append(setCond.getOperator().toUpperCase(Locale.ROOT)).append(' ');
141 subSb = toSolrQueryString(iterator.next(), usedFields, true);
142 sb.append(subSb);
143 }
144 sb.append(")");
145 return sb;
146 }
147
148 @SuppressWarnings("rawtypes")
149 private static StringBuilder handleSetConditionDefault(MCRSetCondition<MCRCondition> setCond,
150 Set<String> usedFields,
151 boolean subCondition) {
152 boolean stripPlus;
153 if (setCond instanceof MCROrCondition) {
154 stripPlus = true;
155 } else if (setCond instanceof MCRAndCondition) {
156 stripPlus = false;
157 } else {
158 throw new UnsupportedOperationException("Do not know how to handle "
159 + setCond.getClass().getCanonicalName() + " set operation.");
160 }
161 List<MCRCondition<MCRCondition>> children = setCond.getChildren();
162 if (children.isEmpty()) {
163 return null;
164 }
165 StringBuilder sb = new StringBuilder();
166 boolean groupRequired = subCondition || setCond instanceof MCROrCondition;
167 if (groupRequired) {
168 sb.append("+(");
169 }
170 Iterator<MCRCondition<MCRCondition>> iterator = children.iterator();
171 StringBuilder subSb = toSolrQueryString(iterator.next(), usedFields, true);
172 sb.append(stripPlus ? stripPlus(subSb) : subSb);
173 while (iterator.hasNext()) {
174 sb.append(" ");
175 subSb = toSolrQueryString(iterator.next(), usedFields, true);
176 sb.append(stripPlus ? stripPlus(subSb) : subSb);
177 }
178 if (groupRequired) {
179 sb.append(")");
180 }
181 return sb;
182 }
183
184 @SuppressWarnings("rawtypes")
185 private static StringBuilder handleNotCondition(MCRNotCondition notCond, Set<String> usedFields) {
186 MCRCondition child = notCond.getChild();
187 StringBuilder sb = new StringBuilder();
188 sb.append("-");
189 StringBuilder solrQueryString = toSolrQueryString(child, usedFields, true);
190 if (!explicitAndOrMapping()) {
191 stripPlus(solrQueryString);
192 }
193 if (solrQueryString == null || solrQueryString.length() == 0) {
194 return null;
195 }
196 sb.append(solrQueryString);
197 return sb;
198 }
199
200 private static StringBuilder getRangeQuery(String field, String lowerTerm, boolean includeLower, String upperTerm,
201 boolean includeUpper) {
202 StringBuilder sb = new StringBuilder();
203 sb.append('+');
204 sb.append(field);
205 sb.append(":");
206 sb.append(includeLower ? '[' : '{');
207 sb.append(
208 lowerTerm != null ? ("*".equals(lowerTerm) ? "\\*" : MCRSolrUtils.escapeSearchValue(lowerTerm)) : "*");
209 sb.append(" TO ");
210 sb.append(
211 upperTerm != null ? ("*".equals(upperTerm) ? "\\*" : MCRSolrUtils.escapeSearchValue(upperTerm)) : "*");
212 sb.append(includeUpper ? ']' : '}');
213 return sb;
214 }
215
216 public static StringBuilder getLTQuery(String field, String value) {
217 return getRangeQuery(field, null, true, value, false);
218 }
219
220 public static StringBuilder getLTEQuery(String field, String value) {
221 return getRangeQuery(field, null, true, value, true);
222 }
223
224 public static StringBuilder getGTQuery(String field, String value) {
225 return getRangeQuery(field, value, false, null, true);
226 }
227
228 public static StringBuilder getGTEQuery(String field, String value) {
229 return getRangeQuery(field, value, true, null, true);
230 }
231
232 public static StringBuilder getTermQuery(String field, String value) {
233 if (value.length() == 0) {
234 return null;
235 }
236 StringBuilder sb = new StringBuilder();
237 if (!explicitAndOrMapping()) {
238 sb.append('+');
239 }
240 sb.append(field);
241 sb.append(":");
242 String replaced = value.replaceAll("\\s+", " AND ");
243 if (value.length() == replaced.length()) {
244 sb.append(MCRSolrUtils.escapeSearchValue(value));
245 } else {
246 sb.append("(");
247 sb.append(MCRSolrUtils.escapeSearchValue(replaced));
248 sb.append(")");
249 }
250 return sb;
251 }
252
253 public static StringBuilder getPhraseQuery(String field, String value) {
254 StringBuilder sb = new StringBuilder();
255 if (!explicitAndOrMapping()) {
256 sb.append('+');
257 }
258 sb.append(field);
259 sb.append(":");
260 sb.append('"');
261 sb.append(MCRSolrUtils.escapeSearchValue(value));
262 sb.append('"');
263 return sb;
264 }
265
266 private static StringBuilder stripPlus(StringBuilder sb) {
267 if (sb == null || sb.length() == 0) {
268 return sb;
269 }
270 if (sb.charAt(0) == '+') {
271 sb.deleteCharAt(0);
272 }
273 return sb;
274 }
275
276 public static SolrQuery getSolrQuery(@SuppressWarnings("rawtypes") MCRCondition condition, List<MCRSortBy> sortBy,
277 int maxResults, List<String> returnFields) {
278 String queryString = getQueryString(condition);
279 SolrQuery q = applySortOptions(new SolrQuery(queryString), sortBy);
280 q.setIncludeScore(true);
281 q.setRows(maxResults == 0 ? Integer.MAX_VALUE : maxResults);
282
283 if (returnFields != null) {
284 q.setFields(returnFields.size() > 0 ? returnFields.stream().collect(Collectors.joining(",")) : "*");
285 }
286 String sort = q.getSortField();
287 LOGGER.info("MyCoRe Query transformed to: {}{} {}", q.getQuery(), sort != null ? " " + sort : "",
288 q.getFields());
289 return q;
290 }
291
292 public static String getQueryString(@SuppressWarnings("rawtypes") MCRCondition condition) {
293 Set<String> usedFields = new HashSet<>();
294 return MCRConditionTransformer.toSolrQueryString(condition, usedFields);
295 }
296
297 public static SolrQuery applySortOptions(SolrQuery q, List<MCRSortBy> sortBy) {
298 for (MCRSortBy option : sortBy) {
299 SortClause sortClause = new SortClause(option.getFieldName(), option.getSortOrder() ? ORDER.asc
300 : ORDER.desc);
301 q.addSort(sortClause);
302 }
303 return q;
304 }
305
306
307
308
309
310
311
312
313
314
315
316 @SuppressWarnings("rawtypes")
317 public static SolrQuery buildMergedSolrQuery(List<MCRSortBy> sortBy, boolean not, boolean and,
318 HashMap<String, List<MCRCondition>> table, int maxHits, List<String> returnFields) {
319 List<MCRCondition> queryConditions = table.get("metadata");
320 MCRCondition combined = buildSubCondition(queryConditions, and, not);
321 SolrQuery solrRequestQuery = getSolrQuery(combined, sortBy, maxHits, returnFields);
322
323 for (Map.Entry<String, List<MCRCondition>> mapEntry : table.entrySet()) {
324 if (!mapEntry.getKey().equals("metadata")) {
325 MCRCondition combinedFilterQuery = buildSubCondition(mapEntry.getValue(), and, not);
326 SolrQuery filterQuery = getSolrQuery(combinedFilterQuery, sortBy, maxHits, returnFields);
327 solrRequestQuery.addFilterQuery(MCRSolrConstants.SOLR_JOIN_PATTERN + filterQuery.getQuery());
328 }
329 }
330 return solrRequestQuery;
331 }
332
333
334 @SuppressWarnings({ "rawtypes", "unchecked" })
335 protected static MCRCondition buildSubCondition(List<MCRCondition> conditions, boolean and, boolean not) {
336 MCRCondition subCond;
337 if (conditions.size() == 1) {
338 subCond = conditions.get(0);
339 } else if (and) {
340 subCond = new MCRAndCondition().addAll(conditions);
341 } else {
342 subCond = new MCROrCondition().addAll(conditions);
343 }
344 if (not) {
345 subCond = new MCRNotCondition(subCond);
346 }
347 return subCond;
348 }
349
350
351
352
353
354 @SuppressWarnings("rawtypes")
355 public static HashMap<String, List<MCRCondition>> groupConditionsByIndex(MCRSetCondition cond) {
356 HashMap<String, List<MCRCondition>> table = new HashMap<>();
357 @SuppressWarnings("unchecked")
358 List<MCRCondition> children = cond.getChildren();
359
360 for (MCRCondition child : children) {
361 String index = getIndex(child);
362 table.computeIfAbsent(index, k -> new ArrayList<>()).add(child);
363 }
364 return table;
365 }
366
367
368
369
370
371 @SuppressWarnings("rawtypes")
372 private static String getIndex(MCRCondition cond) {
373 if (cond instanceof MCRQueryCondition) {
374 MCRQueryCondition queryCondition = ((MCRQueryCondition) cond);
375 String fieldName = queryCondition.getFieldName();
376 return getIndex(fieldName);
377 } else if (cond instanceof MCRNotCondition) {
378 return getIndex(((MCRNotCondition) cond).getChild());
379 }
380
381 @SuppressWarnings("unchecked")
382 List<MCRCondition> children = ((MCRSetCondition) cond).getChildren();
383
384
385 return children.stream()
386 .map(MCRConditionTransformer::getIndex)
387 .reduce((l, r) -> l.equals(r) ? l : MIXED)
388 .get();
389 }
390
391 public static String getIndex(String fieldName) {
392 return getJoinFields().contains(fieldName) ? "content" : "metadata";
393 }
394
395 private static HashSet<String> getJoinFields() {
396 if (joinFields == null) {
397 joinFields = MCRConfiguration2.getString(SOLR_CONFIG_PREFIX + "JoinQueryFields")
398 .stream()
399 .flatMap(MCRConfiguration2::splitValue)
400 .collect(Collectors.toCollection(HashSet::new));
401 }
402 return joinFields;
403 }
404
405 }