View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.solr.search;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Enumeration;
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.StringTokenizer;
28  
29  import org.apache.logging.log4j.LogManager;
30  import org.apache.logging.log4j.Logger;
31  import org.jdom2.Document;
32  import org.jdom2.Element;
33  import org.jdom2.filter.ElementFilter;
34  import org.mycore.common.MCRUsageException;
35  import org.mycore.parsers.bool.MCRAndCondition;
36  import org.mycore.parsers.bool.MCRCondition;
37  import org.mycore.parsers.bool.MCROrCondition;
38  import org.mycore.parsers.bool.MCRSetCondition;
39  import org.mycore.services.fieldquery.MCRQuery;
40  import org.mycore.services.fieldquery.MCRQueryCondition;
41  import org.mycore.services.fieldquery.MCRQueryParser;
42  import org.mycore.services.fieldquery.MCRSortBy;
43  
44  import jakarta.servlet.http.HttpServletRequest;
45  
46  public class MCRQLSearchUtils {
47  
48      private static final Logger LOGGER = LogManager.getLogger(MCRQLSearchUtils.class);
49  
50      private static HashSet<String> SEARCH_PARAMETER = new HashSet<>(Arrays.asList("search", "query", "maxResults",
51          "numPerPage", "page", "mask", "mode", "redirect", "qt"));
52  
53      /**
54       * Build MCRQuery from editor XML input
55       */
56      public static MCRQuery buildFormQuery(Element root) {
57          Element conditions = root.getChild("conditions");
58  
59          if (conditions.getAttributeValue("format", "xml").equals("xml")) {
60              Element condition = conditions.getChildren().get(0);
61              renameElements(condition);
62  
63              // Remove conditions without values
64              List<Element> empty = new ArrayList<>();
65              for (Iterator<Element> it = conditions.getDescendants(new ElementFilter("condition")); it.hasNext();) {
66                  Element cond = it.next();
67                  if (cond.getAttribute("value") == null) {
68                      empty.add(cond);
69                  }
70              }
71  
72              // Remove empty sort conditions
73              Element sortBy = root.getChild("sortBy");
74              if (sortBy != null) {
75                  for (Element field : sortBy.getChildren("field")) {
76                      if (field.getAttributeValue("name", "").length() == 0) {
77                          empty.add(field);
78                      }
79                  }
80              }
81  
82              for (int i = empty.size() - 1; i >= 0; i--) {
83                  empty.get(i).detach();
84              }
85  
86              if (sortBy != null && sortBy.getChildren().size() == 0) {
87                  sortBy.detach();
88              }
89  
90              // Remove empty returnFields
91              Element returnFields = root.getChild("returnFields");
92              if (returnFields != null && returnFields.getText().length() == 0) {
93                  returnFields.detach();
94              }
95          }
96  
97          return MCRQuery.parseXML(root.getDocument());
98      }
99  
100     /**
101      * Rename elements conditionN to condition. Transform condition with multiple child values to OR-condition.
102      */
103     protected static void renameElements(Element element) {
104         if (element.getName().startsWith("condition")) {
105             element.setName("condition");
106 
107             String field = new StringTokenizer(element.getAttributeValue("field"), " -,").nextToken();
108             String operator = element.getAttributeValue("operator");
109             if (operator == null) {
110                 LOGGER.warn("No operator defined for field: {}", field);
111                 operator = "=";
112             }
113             element.setAttribute("operator", operator);
114 
115             List<Element> values = element.getChildren("value");
116             if (values != null && values.size() > 0) {
117                 element.removeAttribute("field");
118                 element.setAttribute("operator", "or");
119                 element.setName("boolean");
120                 for (Element value : values) {
121                     value.setName("condition");
122                     value.setAttribute("field", field);
123                     value.setAttribute("operator", operator);
124                     value.setAttribute("value", value.getText());
125                     value.removeContent();
126                 }
127             }
128         } else if (element.getName().startsWith("boolean")) {
129             element.setName("boolean");
130             for (Object child : element.getChildren()) {
131                 if (child instanceof Element) {
132                     renameElements((Element) child);
133                 }
134             }
135         }
136     }
137 
138     /**
139      * Search using complex query expression given as text string
140      */
141     public static MCRQuery buildComplexQuery(String query) {
142         return new MCRQuery(new MCRQueryParser().parse(query));
143     }
144 
145     /**
146      * Search in default search field specified by MCR.SearchServlet.DefaultSearchField
147      */
148     @SuppressWarnings({ "rawtypes", "unchecked" })
149     public static MCRQuery buildDefaultQuery(String search, String defaultSearchField) {
150         String[] fields = defaultSearchField.split(" *, *");
151         MCROrCondition queryCondition = new MCROrCondition();
152 
153         for (String fDef : fields) {
154             MCRCondition condition = new MCRQueryCondition(fDef, "=", search);
155             queryCondition.addChild(condition);
156         }
157 
158         return new MCRQuery(MCRQueryParser.normalizeCondition(queryCondition));
159     }
160 
161     /**
162      * Search using name=value pairs from HTTP request
163      */
164     @SuppressWarnings({ "rawtypes", "unchecked" })
165     public static MCRQuery buildNameValueQuery(HttpServletRequest req) {
166         MCRAndCondition condition = new MCRAndCondition();
167 
168         for (Enumeration<String> names = req.getParameterNames(); names.hasMoreElements();) {
169             String name = names.nextElement();
170             if (name.endsWith(".operator")) {
171                 continue;
172             }
173             if (name.contains(".sortField")) {
174                 continue;
175             }
176             if (SEARCH_PARAMETER.contains(name)) {
177                 continue;
178             }
179             if (name.startsWith("XSL.")) {
180                 continue;
181             }
182 
183             String[] values = req.getParameterValues(name);
184             MCRSetCondition parent = condition;
185 
186             if ((values.length > 1) || name.contains(",")) {
187                 // Multiple fields with same name, combine with OR
188                 parent = new MCROrCondition();
189                 condition.addChild(parent);
190             }
191 
192             for (String fieldName : name.split(",")) {
193                 String operator = getReqParameter(req, fieldName + ".operator", "=");
194                 for (String value : values) {
195                     parent.addChild(new MCRQueryCondition(fieldName, operator, value));
196                 }
197             }
198         }
199 
200         if (condition.getChildren().isEmpty()) {
201             throw new MCRUsageException("Missing query condition");
202         }
203 
204         return new MCRQuery(MCRQueryParser.normalizeCondition(condition));
205     }
206 
207     protected static Document setQueryOptions(MCRQuery query, HttpServletRequest req) {
208         String maxResults = getReqParameter(req, "maxResults", "0");
209         query.setMaxResults(Integer.parseInt(maxResults));
210 
211         List<String> sortFields = new ArrayList<>();
212         for (Enumeration<String> names = req.getParameterNames(); names.hasMoreElements();) {
213             String name = names.nextElement();
214             if (name.contains(".sortField")) {
215                 sortFields.add(name);
216             }
217         }
218 
219         if (sortFields.size() > 0) {
220             sortFields.sort((arg0, arg1) -> {
221                 String s0 = arg0.substring(arg0.indexOf(".sortField"));
222                 String s1 = arg1.substring(arg1.indexOf(".sortField"));
223                 return s0.compareTo(s1);
224             });
225             List<MCRSortBy> sortBy = new ArrayList<>();
226             for (String name : sortFields) {
227                 String sOrder = getReqParameter(req, name, "ascending");
228                 boolean order = "ascending".equals(sOrder) ? MCRSortBy.ASCENDING : MCRSortBy.DESCENDING;
229                 String fieldName = name.substring(0, name.indexOf(".sortField"));
230                 sortBy.add(new MCRSortBy(fieldName, order));
231             }
232             query.setSortBy(sortBy);
233         }
234 
235         Document xml = query.buildXML();
236         xml.getRootElement().setAttribute("numPerPage", getReqParameter(req, "numPerPage", "0"));
237         xml.getRootElement().setAttribute("mask", getReqParameter(req, "mask", "-"));
238         return xml;
239     }
240 
241     protected static String getReqParameter(HttpServletRequest req, String name, String defaultValue) {
242         String value = req.getParameter(name);
243         if (value == null || value.trim().length() == 0) {
244             return defaultValue;
245         } else {
246             return value.trim();
247         }
248     }
249 }