1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.search.solr.internal

File XWikiDismaxQParserPlugin.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

32
56
9
1
264
128
26
0.46
6.22
9
2.89

Classes

Class Line # Actions
XWikiDismaxQParserPlugin 63 56 0% 26 8
0.9175257791.8%
 

Contributing tests

This file is covered by 5 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software 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 GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.search.solr.internal;
21   
22    import java.util.ArrayList;
23    import java.util.Arrays;
24    import java.util.Collections;
25    import java.util.HashMap;
26    import java.util.HashSet;
27    import java.util.List;
28    import java.util.Map;
29    import java.util.Set;
30    import java.util.regex.Matcher;
31    import java.util.regex.Pattern;
32   
33    import org.apache.solr.common.params.MapSolrParams;
34    import org.apache.solr.common.params.SolrParams;
35    import org.apache.solr.request.SolrQueryRequest;
36    import org.apache.solr.search.ExtendedDismaxQParserPlugin;
37    import org.apache.solr.search.QParser;
38    import org.apache.solr.util.SolrPluginUtils;
39   
40    /**
41    * Extends {@link ExtendedDismaxQParserPlugin} in order to add dynamic aliases for multilingual fields which are
42    * expanded in the search query. This way, a user can write a query on the {@code title} field and all the
43    * {@code title_<language>} variations of the field will be used in the query. The list of languages for which a field
44    * is expanded is taken from the {@code xwiki.supportedLocales} query parameter. If this parameter is not defined, the
45    * ROOT locale is used instead. The list of multilingual fields is determined based on the
46    * {@code xwiki.multilingualFields}.
47    * <p>
48    * The current approach is to extract the field names from the search query and to add the alias parameters before the
49    * query is parsed. We tried the following solutions too, but they failed:
50    * <ul>
51    * <li>We tried to extended {@code ExtendedDismaxQParser} and override {@code getFieldName()} in order to detect the
52    * fields that appear in the search query and add the alias parameters for them but unfortunately
53    * {@code ExtendedDismaxQParser} calls {@code ExtendedSolrQueryParser#addAliasesFromRequest()} before splitting the
54    * search query in clauses.</li>
55    * <li>We tried to expand the {@code Query} object returned by {@code ExtendedSolrQueryParser#parse()} but it doesn't
56    * support iteration and it has lots of subclasses so we had to check the type of query and perform special iteration
57    * and special changes for each of these subclasses.</li>
58    * </ul>
59    *
60    * @version $Id: 5ad15c0d4c5609e8cd06194ea4c3e5127e776828 $
61    * @since 5.3RC1
62    */
 
63    public class XWikiDismaxQParserPlugin extends ExtendedDismaxQParserPlugin
64    {
65    /**
66    * The pattern used to split list configuration parameters.
67    */
68    private static final Pattern LIST_SEPARATOR = Pattern.compile("\\s*,\\s*");
69   
70    /**
71    * The pattern used to extract field names from a search query. The field name starts with a lower case letter (the
72    * names of dynamic fields should start with a prefix) or with underscore and can contain Unicode letters and
73    * digits, plus also the following special characters: '_' (underscore), '-' (dash), '.' (dot) and '$' (dollar).
74    * Also, the field name appears either at the start of the query or after one of these: '+' (plus), '-' (minus), '('
75    * (round left bracket) or a white space.
76    *
77    * @see ExtendedDismaxQParser#getFieldName()
78    */
79    private static final Pattern FIELD_PATTERN = Pattern.compile("(?:^|[+\\-(\\s])([a-z_][\\p{L}\\p{N}_\\-.$]*):");
80   
81    /**
82    * The string used to define a dynamic field.
83    */
84    private static final String WILDCARD = "*";
85   
 
86  1020 toggle @Override
87    public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req)
88    {
89  1020 return super.createParser(qstr, localParams, withFieldAliases(qstr, params), req);
90    }
91   
92    /**
93    * Extends the given search parameters with aliases for the fields that appear in the given search query.
94    *
95    * @param query the search query where to look for field names
96    * @param parameters the search parameters to extend
97    * @return the extended search parameters
98    */
 
99  1024 toggle public SolrParams withFieldAliases(String query, SolrParams parameters)
100    {
101  1024 Set<String> fieldNames = extractFieldNames(query);
102    // Add default query fields (these fields are used to search for free text that appears in the query).
103  1024 String defaultQueryFields = parameters.get("qf");
104  1024 if (defaultQueryFields != null) {
105  1024 fieldNames.addAll(SolrPluginUtils.parseFieldBoosts(defaultQueryFields).keySet());
106    }
107  1024 if (fieldNames.isEmpty()) {
108  0 return parameters;
109    }
110   
111  1024 Map<String, String> aliasParameters = new HashMap<String, String>();
112  1024 addMultilingualFieldAliases(fieldNames, aliasParameters, parameters);
113  1024 addTypedDynamicFieldAliases(fieldNames, aliasParameters, parameters);
114   
115  1024 return aliasParameters.isEmpty() ? parameters : SolrParams.wrapDefaults(new MapSolrParams(aliasParameters),
116    parameters);
117    }
118   
119    /**
120    * Adds aliases for multilingual fields.
121    *
122    * @param fieldNames the set of field names to add aliases for
123    * @param aliasParameters the map where the aliases are collected
124    * @param parameters the search query parameters used to extract the list of multilingual fields and the list of
125    * supported locales
126    */
 
127  1024 toggle private void addMultilingualFieldAliases(Set<String> fieldNames, Map<String, String> aliasParameters,
128    SolrParams parameters)
129    {
130  1024 List<String> multilingualFields = getListParameter("xwiki.multilingualFields", parameters);
131  1024 if (multilingualFields.isEmpty()) {
132  1 return;
133    }
134   
135    // There is at least one supported locale, the ROOT locale.
136  1023 List<String> supportedLocales = getSupportedLocales(parameters);
137   
138  1023 for (String fieldName : fieldNames) {
139  12980 if (matchesFieldName(fieldName, multilingualFields)) {
140  7096 addAliases(fieldName, supportedLocales, aliasParameters);
141    }
142    }
143    }
144   
145    /**
146    * Adds aliases for typed dynamic fields.
147    * <p>
148    * The names of the non-string dynamic fields must be suffixed with the data type (instead of the locale) in order
149    * for them to be indexed correctly. Thus we need to add aliases for dynamic field names that will match the
150    * configured data types.
151    *
152    * @param fieldNames the set of field names to add aliases for
153    * @param aliasParameters the map where the aliases are collected
154    * @param parameters the search query parameters used to extract the list of typed dynamic fields and the list of
155    * supported data types
156    */
 
157  1024 toggle private void addTypedDynamicFieldAliases(Set<String> fieldNames, Map<String, String> aliasParameters,
158    SolrParams parameters)
159    {
160  1024 List<String> typedDynamicFields = getListParameter("xwiki.typedDynamicFields", parameters);
161  1024 List<String> dynamicFieldTypes = getListParameter("xwiki.dynamicFieldTypes", parameters);
162  1024 if (typedDynamicFields.isEmpty() || dynamicFieldTypes.isEmpty()) {
163  2 return;
164    }
165   
166  1022 for (String fieldName : fieldNames) {
167  12978 if (matchesFieldName(fieldName, typedDynamicFields)) {
168  289 addAliases(fieldName, dynamicFieldTypes, aliasParameters);
169    }
170    }
171    }
172   
173    /**
174    * Extracts the field names from the given search query.
175    *
176    * @param query the search query
177    * @return the set of field names
178    */
 
179  1035 toggle public Set<String> extractFieldNames(String query)
180    {
181  1035 Set<String> fieldNames = new HashSet<String>();
182  1035 Matcher matcher = FIELD_PATTERN.matcher(query);
183  1051 while (matcher.find()) {
184  16 fieldNames.add(matcher.group(1));
185    }
186  1035 return fieldNames;
187    }
188   
189    /**
190    * Get the value of a list parameter.
191    *
192    * @param parameter the name of a list parameter (its value is a comma-separated list of strings)
193    * @param parameters the query parameters
194    * @return the list value
195    */
 
196  3072 toggle private static List<String> getListParameter(String parameter, SolrParams parameters)
197    {
198  3072 String value = parameters.get(parameter);
199  3072 if (value != null) {
200  3067 return Arrays.asList(LIST_SEPARATOR.split(value));
201    } else {
202  5 return Collections.emptyList();
203    }
204    }
205   
206    /**
207    * @param parameters the query parameters
208    * @return the list of supported locales
209    */
 
210  1023 toggle private static List<String> getSupportedLocales(SolrParams parameters)
211    {
212  1023 List<String> supportedLocalesList = new ArrayList<String>();
213  1023 supportedLocalesList.add("_");
214  1023 String supportedLocales = parameters.get("xwiki.supportedLocales");
215  1023 if (supportedLocales != null) {
216  52 supportedLocalesList.addAll(Arrays.asList(LIST_SEPARATOR.split(supportedLocales)));
217    }
218  1023 return supportedLocalesList;
219    }
220   
221    /**
222    * @param fieldName the field name to match
223    * @param fieldNamePatterns the list of field name patterns; a field name pattern is a string that can start or
224    * end with a {@link #WILDCARD}.
225    * @return {@code true} if at least one of the field name patterns matches the given field name, {@code false}
226    * otherwise
227    */
 
228  25958 toggle private boolean matchesFieldName(String fieldName, List<String> fieldNamePatterns)
229    {
230  25958 for (String fieldNamePattern : fieldNamePatterns) {
231  95431 if (fieldNamePattern.equals(fieldName)) {
232  6807 return true;
233  88624 } else if (fieldNamePattern.endsWith(WILDCARD)) {
234  25031 if (fieldName.startsWith(fieldNamePattern.substring(0, fieldNamePattern.length() - 1))) {
235  578 return true;
236    }
237  63593 } else if (fieldNamePattern.startsWith(WILDCARD)) {
238  0 if (fieldName.endsWith(fieldNamePattern.substring(1))) {
239  0 return true;
240    }
241    }
242    }
243  18573 return false;
244    }
245   
246    /**
247    * Adds aliases for the specified field to the given parameters.
248    *
249    * @param fieldName a field name
250    * @param suffixes the list of alias suffixes
251    * @param aliasParameters where to add the aliases
252    */
 
253  7385 toggle private void addAliases(String fieldName, List<String> suffixes, Map<String, String> aliasParameters)
254    {
255  7385 String aliasParameterName = String.format("f.%s.qf", fieldName);
256  7385 StringBuilder aliasParameterValue = new StringBuilder();
257  7385 for (String suffix : suffixes) {
258  9425 aliasParameterValue.append(' ').append(fieldName).append('_').append(suffix);
259    }
260  7385 String previousValue = aliasParameters.get(aliasParameterName);
261  7385 aliasParameters.put(aliasParameterName, previousValue == null ? aliasParameterValue.substring(1)
262    : previousValue + aliasParameterValue.toString());
263    }
264    }