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

File SolrQueryExecutor.java

 

Coverage histogram

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

Code metrics

20
54
5
1
266
138
22
0.41
10.8
5
4.4

Classes

Class Line # Actions
SolrQueryExecutor 64 54 0% 22 7
0.911392491.1%
 

Contributing tests

This file is covered by 2 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.query.solr.internal;
21   
22    import java.lang.reflect.Array;
23    import java.util.ArrayList;
24    import java.util.Arrays;
25    import java.util.List;
26    import java.util.Map.Entry;
27   
28    import javax.inject.Inject;
29    import javax.inject.Named;
30    import javax.inject.Provider;
31    import javax.inject.Singleton;
32   
33    import org.apache.commons.lang.StringUtils;
34    import org.apache.solr.client.solrj.SolrQuery;
35    import org.apache.solr.client.solrj.response.QueryResponse;
36    import org.apache.solr.common.SolrDocument;
37    import org.apache.solr.common.SolrDocumentList;
38    import org.slf4j.Logger;
39    import org.xwiki.bridge.DocumentAccessBridge;
40    import org.xwiki.component.annotation.Component;
41    import org.xwiki.job.event.status.JobProgressManager;
42    import org.xwiki.model.reference.DocumentReference;
43    import org.xwiki.model.reference.DocumentReferenceResolver;
44    import org.xwiki.query.Query;
45    import org.xwiki.query.QueryException;
46    import org.xwiki.query.QueryExecutor;
47    import org.xwiki.query.SecureQuery;
48    import org.xwiki.search.solr.internal.api.SolrInstance;
49   
50    import com.xpn.xwiki.XWikiContext;
51   
52    /**
53    * Executes Solr queries.
54    * <p>
55    * For now, the result is the direct {@link QueryResponse}, in lack of a more expressive result type than the generic
56    * List that the {@link #execute(Query)} method allows.
57    *
58    * @version $Id: cc90315c1dc46ef364ac38306a2f03d239f41b83 $
59    * @since 4.3M2
60    */
61    @Component
62    @Named(SolrQueryExecutor.SOLR)
63    @Singleton
 
64    public class SolrQueryExecutor implements QueryExecutor
65    {
66    /**
67    * Query language ID.
68    */
69    public static final String SOLR = "solr";
70   
71    /**
72    * The parameter that specifies the list of supported locales. This is used to add generic (unlocalized) aliases for
73    * localized query fields (e.g. 'title' alias for 'title_en' query field).
74    */
75    private static final String PARAM_SUPPORTED_LOCALES = "xwiki.supportedLocales";
76   
77    /**
78    * Logging framework.
79    */
80    @Inject
81    protected Logger logger;
82   
83    /**
84    * XWiki model bridge.
85    */
86    @Inject
87    protected DocumentAccessBridge documentAccessBridge;
88   
89    /**
90    * Provider for the {@link SolrInstance} that allows communication with the Solr server.
91    */
92    @Inject
93    protected Provider<SolrInstance> solrInstanceProvider;
94   
95    /**
96    * Used to retrieve the configured supported locales.
97    */
98    @Inject
99    private Provider<XWikiContext> xcontextProvider;
100   
101    /**
102    * Used to extract a {@link DocumentReference} from a {@link SolrDocument}.
103    */
104    @Inject
105    private DocumentReferenceResolver<SolrDocument> solrDocumentReferenceResolver;
106   
107    @Inject
108    private JobProgressManager progress;
109   
 
110  52 toggle @Override
111    public <T> List<T> execute(Query query) throws QueryException
112    {
113    // TODO: make it less restrictive, see http://jira.xwiki.org/browse/XWIKI-9386
114  52 if (query instanceof SecureQuery && ((SecureQuery) query).isCurrentAuthorChecked()
115    && !this.documentAccessBridge.hasProgrammingRights()) {
116  0 throw new QueryException("Solr query require programming right", query, null);
117    }
118   
119  52 this.progress.startStep(query, "query.solr.progress.execute", "Execute Solr query [{}]", query);
120  52 this.progress.pushLevelProgress(3, query);
121   
122  52 try {
123  52 this.progress.startStep(query, "query.solr.progress.execute.prepare", "Prepare");
124   
125  52 SolrInstance solrInstance = solrInstanceProvider.get();
126  52 SolrQuery solrQuery = createSolrQuery(query);
127   
128  52 this.progress.startStep(query, "query.solr.progress.execute.execute", "Execute");
129   
130  52 QueryResponse response = solrInstance.query(solrQuery);
131   
132  52 this.progress.startStep(query, "query.solr.progress.execute.filter", "Filter");
133   
134    // Check access rights need to be checked before returning the response.
135    // FIXME: this is not really the best way, mostly because at this point all grouping operations
136    // have already been performed and any change on the result will not ensure that the grouping
137    // information (facets, highlighting, maxScore, etc.) is still relevant.
138    // A better way would be using a PostFilter as described in this article:
139    // http://java.dzone.com/articles/custom-security-filtering-solr
140    // Basically, we would be asking
141  52 if (!(query instanceof SecureQuery) || ((SecureQuery) query).isCurrentUserChecked()) {
142  3 this.filterResponse(response);
143    }
144   
145  52 return (List<T>) Arrays.asList(response);
146    } catch (Exception e) {
147  0 throw new QueryException("Exception while executing query", query, e);
148    } finally {
149  52 this.progress.popLevelProgress(query);
150  52 this.progress.endStep(query);
151    }
152    }
153   
 
154  52 toggle private SolrQuery createSolrQuery(Query query)
155    {
156  52 SolrQuery solrQuery = new SolrQuery(query.getStatement());
157   
158    // Overwrite offset and limit only if the query object explicitly says so, otherwise use whatever the query
159    // statement says or the defaults.
160  52 if (query.getOffset() > 0) {
161  1 solrQuery.setStart(query.getOffset());
162    }
163  52 if (query.getLimit() > 0) {
164  43 solrQuery.setRows(query.getLimit());
165    }
166   
167    // TODO: good idea? Any confusion? Do we really needs something like this?
168    // Reuse the Query.getNamedParameters() map to get extra parameters.
169  52 for (Entry<String, Object> entry : query.getNamedParameters().entrySet()) {
170  200 Object value = entry.getValue();
171   
172  200 if (value instanceof Iterable) {
173  99 solrQuery.set(entry.getKey(), toStringArray((Iterable) value));
174  101 } else if (value != null && value.getClass().isArray()) {
175  2 solrQuery.set(entry.getKey(), toStringArray(value));
176    } else {
177  99 solrQuery.set(entry.getKey(), String.valueOf(value));
178    }
179    }
180   
181    // Make sure the list of supported locales is set so the names of the fields that are indexed in multiple
182    // languages are expanded in the search query. For instance, the query "title:text" will be expanded to
183    // "title__:text OR title_en:text OR title_fr:text" if the list of supported locales is [en, fr].
184  52 if (!solrQuery.getParameterNames().contains(PARAM_SUPPORTED_LOCALES)) {
185  52 XWikiContext xcontext = this.xcontextProvider.get();
186  52 solrQuery.set(PARAM_SUPPORTED_LOCALES,
187    StringUtils.join(xcontext.getWiki().getAvailableLocales(xcontext), ","));
188    }
189   
190  52 return solrQuery;
191    }
192   
193    /**
194    * Converts an arbitrary array to an array containing its string representations.
195    *
196    * @param array an array of arbitrary type, must not be null
197    * @return an array with the string representations of the passed array's items
198    */
 
199  2 toggle private String[] toStringArray(Object array)
200    {
201  2 int length = Array.getLength(array);
202  2 String[] args = new String[length];
203  6 for (int i = 0; i < length; i++) {
204  4 args[i] = String.valueOf(Array.get(array, i));
205    }
206   
207  2 return args;
208    }
209   
210    /**
211    * Converts the given iterable object to an array containing its string representations.
212    *
213    * @param iterable the iterable object, must not be null
214    * @return an array with the string representations of the passed iterable's items
215    */
 
216  99 toggle private String[] toStringArray(Iterable iterable)
217    {
218  99 List<String> args = new ArrayList<String>();
219  99 for (Object obj : iterable) {
220  152 args.add(String.valueOf(obj));
221    }
222  99 return args.toArray(new String[args.size()]);
223    }
224   
225    /**
226    * Filter out results from the response that the current user does not have access to view.
227    *
228    * @param response the Solr response to filter
229    */
 
230  3 toggle protected void filterResponse(QueryResponse response)
231    {
232  3 SolrDocumentList results = response.getResults();
233  3 long numFound = results.getNumFound();
234   
235    // Since we are modifying the results collection, we need to iterate over its copy.
236  3 for (SolrDocument result : new ArrayList<SolrDocument>(results)) {
237  5 try {
238  5 DocumentReference resultDocumentReference = this.solrDocumentReferenceResolver.resolve(result);
239   
240  5 if (!documentAccessBridge.exists(resultDocumentReference)
241    || !documentAccessBridge.isDocumentViewable(resultDocumentReference)) {
242   
243    // Remove the current incompatible result.
244  2 results.remove(result);
245   
246    // Decrement the number of results.
247  2 numFound--;
248   
249    // FIXME: We should update maxScore as well when removing the top scored item. How do we do that?
250    // Sorting based on score might be a not so expensive option.
251   
252    // FIXME: What about highlighting, facets and all the other data inside the QueryResponse?
253    }
254    } catch (Exception e) {
255  0 this.logger.warn("Skipping bad result: {}", result, e);
256    }
257    }
258   
259    // Update the new number of results, excluding the filtered ones.
260  3 if (numFound < 0) {
261    // Lower bound guard for the total number of results.
262  0 numFound = 0;
263    }
264  3 results.setNumFound(numFound);
265    }
266    }