1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.store.hibernate.query

File HqlQueryExecutor.java

 

Coverage histogram

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

Code metrics

58
120
19
1
437
302
58
0.48
6.32
19
3.05

Classes

Class Line # Actions
HqlQueryExecutor 75 120 0% 58 11
0.944162494.4%
 

Contributing tests

This file is covered by 99 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 com.xpn.xwiki.store.hibernate.query;
21   
22    import java.util.Collection;
23    import java.util.Collections;
24    import java.util.HashSet;
25    import java.util.List;
26    import java.util.Map;
27    import java.util.Map.Entry;
28    import java.util.Set;
29   
30    import javax.inject.Inject;
31    import javax.inject.Named;
32    import javax.inject.Provider;
33    import javax.inject.Singleton;
34   
35    import org.apache.commons.lang3.StringUtils;
36    import org.hibernate.SQLQuery;
37    import org.hibernate.Session;
38    import org.hibernate.cfg.Configuration;
39    import org.hibernate.engine.NamedQueryDefinition;
40    import org.hibernate.engine.NamedSQLQueryDefinition;
41    import org.xwiki.component.annotation.Component;
42    import org.xwiki.component.manager.ComponentLookupException;
43    import org.xwiki.component.manager.ComponentManager;
44    import org.xwiki.component.phase.Initializable;
45    import org.xwiki.component.phase.InitializationException;
46    import org.xwiki.context.Execution;
47    import org.xwiki.job.event.status.JobProgressManager;
48    import org.xwiki.query.Query;
49    import org.xwiki.query.QueryException;
50    import org.xwiki.query.QueryExecutor;
51    import org.xwiki.query.QueryFilter;
52    import org.xwiki.query.QueryParameter;
53    import org.xwiki.query.SecureQuery;
54    import org.xwiki.query.WrappingQuery;
55    import org.xwiki.security.authorization.ContextualAuthorizationManager;
56    import org.xwiki.security.authorization.Right;
57   
58    import com.xpn.xwiki.XWikiContext;
59    import com.xpn.xwiki.XWikiException;
60    import com.xpn.xwiki.internal.store.hibernate.query.HqlQueryUtils;
61    import com.xpn.xwiki.store.XWikiHibernateBaseStore.HibernateCallback;
62    import com.xpn.xwiki.store.XWikiHibernateStore;
63    import com.xpn.xwiki.store.hibernate.HibernateSessionFactory;
64    import com.xpn.xwiki.util.Util;
65   
66    /**
67    * QueryExecutor implementation for Hibernate Store.
68    *
69    * @version $Id: 6f844abed5b989d2c3cfd1493d4e62a06c952031 $
70    * @since 1.6M1
71    */
72    @Component
73    @Named("hql")
74    @Singleton
 
75    public class HqlQueryExecutor implements QueryExecutor, Initializable
76    {
77    /**
78    * Path to Hibernate mapping with named queries. Configured via component manager.
79    */
80    private static final String MAPPING_PATH = "queries.hbm.xml";
81   
82    private static final String ESCAPE_LIKE_PARAMETERS_FILTER = "escapeLikeParameters";
83   
84    /**
85    * Session factory needed for register named queries mapping.
86    */
87    @Inject
88    private HibernateSessionFactory sessionFactory;
89   
90    /**
91    * Used for access to XWikiContext.
92    */
93    @Inject
94    private Execution execution;
95   
96    @Inject
97    private ContextualAuthorizationManager authorization;
98   
99    @Inject
100    private JobProgressManager progress;
101   
102    @Inject
103    @Named("context")
104    private Provider<ComponentManager> componentManagerProvider;
105   
106    private volatile Set<String> allowedNamedQueries;
107   
 
108  239 toggle @Override
109    public void initialize() throws InitializationException
110    {
111  239 Configuration configuration = this.sessionFactory.getConfiguration();
112   
113  239 configuration.addInputStream(Util.getResourceAsStream(MAPPING_PATH));
114    }
115   
 
116  1 toggle private Set<String> getAllowedNamedQueries()
117    {
118  1 if (this.allowedNamedQueries == null) {
119  1 synchronized (this) {
120  1 if (this.allowedNamedQueries == null) {
121  1 this.allowedNamedQueries = new HashSet<>();
122   
123  1 Configuration configuration = this.sessionFactory.getConfiguration();
124   
125    // Gather the list of allowed named queries
126  1 Map<String, NamedQueryDefinition> namedQueries = configuration.getNamedQueries();
127  1 for (Map.Entry<String, NamedQueryDefinition> query : namedQueries.entrySet()) {
128  0 if (HqlQueryUtils.isSafe(query.getValue().getQuery())) {
129  0 this.allowedNamedQueries.add(query.getKey());
130    }
131    }
132    }
133    }
134    }
135   
136  1 return this.allowedNamedQueries;
137    }
138   
139    /**
140    * @param statementString the statement to evaluate
141    * @return true if the select is allowed for user without PR
142    */
 
143  401 toggle protected static boolean isSafeSelect(String statementString)
144    {
145  401 return HqlQueryUtils.isShortFormStatement(statementString) || HqlQueryUtils.isSafe(statementString);
146    }
147   
 
148  136959 toggle protected void checkAllowed(final Query query) throws QueryException
149    {
150  136961 if (query instanceof SecureQuery && ((SecureQuery) query).isCurrentAuthorChecked()) {
151  18018 if (!this.authorization.hasAccess(Right.PROGRAM)) {
152  402 if (query.isNamed() && !getAllowedNamedQueries().contains(query.getStatement())) {
153  1 throw new QueryException("Named queries requires programming right", query, null);
154    }
155   
156  401 if (!isSafeSelect(query.getStatement())) {
157  3 throw new QueryException("The query requires programming right", query, null);
158    }
159    }
160    }
161    }
162   
 
163  136961 toggle @Override
164    public <T> List<T> execute(final Query query) throws QueryException
165    {
166    // Make sure the query is allowed in the current context
167  136961 checkAllowed(query);
168   
169  136958 String oldDatabase = getContext().getWikiId();
170  136958 try {
171  136957 this.progress.startStep(query, "query.hql.progress.execute", "Execute HQL query [{}]",
172    query);
173   
174  136957 if (query.getWiki() != null) {
175  104452 getContext().setWikiId(query.getWiki());
176    }
177  136957 return getStore().executeRead(getContext(), new HibernateCallback<List<T>>()
178    {
 
179  136882 toggle @SuppressWarnings("unchecked")
180    @Override
181    public List<T> doInHibernate(Session session)
182    {
183  136894 org.hibernate.Query hquery = createHibernateQuery(session, query);
184   
185  136868 List<T> results = hquery.list();
186  136874 if (query.getFilters() != null && !query.getFilters().isEmpty()) {
187  35258 for (QueryFilter filter : query.getFilters()) {
188  64657 results = filter.filterResults(results);
189    }
190    }
191  136867 return results;
192    }
193    });
194    } catch (XWikiException e) {
195  20 throw new QueryException("Exception while executing query", query, e);
196    } finally {
197  136918 getContext().setWikiId(oldDatabase);
198   
199  136947 this.progress.endStep(query);
200    }
201    }
202   
 
203  136878 toggle protected org.hibernate.Query createHibernateQuery(Session session, Query query)
204    {
205  136891 org.hibernate.Query hquery;
206   
207  136896 Query filteredQuery = query;
208  136893 if (!filteredQuery.isNamed()) {
209    // For non-named queries, convert the short form into long form before we apply the filters.
210  133654 filteredQuery = new WrappingQuery(filteredQuery)
211    {
 
212  167449 toggle @Override
213    public String getStatement()
214    {
215    // handle short queries
216  167451 return completeShortFormStatement(getWrappedQuery().getStatement());
217    }
218    };
219  133651 filteredQuery = filterQuery(filteredQuery, Query.HQL);
220  133655 hquery = session.createQuery(filteredQuery.getStatement());
221  133642 populateParameters(hquery, filteredQuery);
222    } else {
223  3241 hquery = createNamedHibernateQuery(session, filteredQuery);
224    }
225   
226  136875 return hquery;
227    }
228   
 
229  135339 toggle private Query filterQuery(Query query, String language)
230    {
231  135359 Query filteredQuery = query;
232   
233    // If there are Query parameters of type QueryParameter then, for convenience, automatically add the
234    // "escapeLikeParameters" filter (if not already there)
235  135362 addEscapeLikeParametersFilter(query);
236   
237  135366 if (query.getFilters() != null && !query.getFilters().isEmpty()) {
238  35260 for (QueryFilter filter : query.getFilters()) {
239    // Step 1: For backward-compatibility reasons call #filterStatement() first
240  64654 String filteredStatement = filter.filterStatement(filteredQuery.getStatement(), language);
241    // Prevent unnecessary creation of WrappingQuery objects when the QueryFilter doesn't modify the
242    // statement.
243  64659 if (!filteredStatement.equals(filteredQuery.getStatement())) {
244  62718 filteredQuery = new WrappingQuery(filteredQuery) {
 
245  93811 toggle @Override
246    public String getStatement()
247    {
248  93809 return filteredStatement;
249    }
250    };
251    }
252    // Step 2: Run #filterQuery()
253  64659 filteredQuery = filter.filterQuery(filteredQuery);
254    }
255    }
256  135362 return filteredQuery;
257    }
258   
 
259  135335 toggle private void addEscapeLikeParametersFilter(Query query)
260    {
261  135352 if (!hasQueryParametersType(query)) {
262  135279 return;
263    }
264   
265    // Find the component class for the "escapeLikeParameters" filter
266  78 QueryFilter escapeFilter;
267  78 try {
268  78 escapeFilter =
269    this.componentManagerProvider.get().getInstance(QueryFilter.class, ESCAPE_LIKE_PARAMETERS_FILTER);
270    } catch (ComponentLookupException e) {
271    // Shouldn't happen!
272  0 throw new RuntimeException(
273    String.format("Failed to locate [%s] Query Filter", ESCAPE_LIKE_PARAMETERS_FILTER), e);
274    }
275   
276  78 boolean found = false;
277  78 for (QueryFilter filter : query.getFilters()) {
278  220 if (escapeFilter.getClass().getName().equals(filter.getClass().getName())) {
279  6 found = true;
280  6 break;
281    }
282    }
283   
284  78 if (!found) {
285  72 query.addFilter(escapeFilter);
286    }
287    }
288   
 
289  135348 toggle private boolean hasQueryParametersType(Query query)
290    {
291  135361 boolean found = false;
292   
293  135364 for (Object value : query.getNamedParameters().values()) {
294  196171 if (value instanceof QueryParameter) {
295  66 found = true;
296  66 break;
297    }
298    }
299  135359 if (!found) {
300  135291 for (Object value : query.getPositionalParameters().values()) {
301  12426 if (value instanceof QueryParameter) {
302  12 found = true;
303  12 break;
304    }
305    }
306    }
307   
308  135361 return found;
309    }
310   
311    /**
312    * Append the required select clause to HQL short query statements. Short statements are the only way for users
313    * without programming rights to perform queries. Such statements can be for example:
314    * <ul>
315    * <li>{@code , BaseObject obj where doc.fullName=obj.name and obj.className='XWiki.MyClass'}</li>
316    * <li>{@code where doc.creationDate > '2008-01-01'}</li>
317    * </ul>
318    *
319    * @param statement the statement to complete if required.
320    * @return the complete statement if it had to be completed, the original one otherwise.
321    */
 
322  167445 toggle protected String completeShortFormStatement(String statement)
323    {
324  167453 String lcStatement = statement.toLowerCase().trim();
325  167447 if (lcStatement.isEmpty() || lcStatement.startsWith(",") || lcStatement.startsWith("where ")
326    || lcStatement.startsWith("order by ")) {
327  36864 return "select doc.fullName from XWikiDocument doc " + statement.trim();
328    }
329   
330  130581 return statement;
331    }
332   
 
333  3240 toggle private org.hibernate.Query createNamedHibernateQuery(Session session, Query query)
334    {
335  3241 org.hibernate.Query hQuery = session.getNamedQuery(query.getStatement());
336  3241 Query filteredQuery = query;
337  3241 if (filteredQuery.getFilters() != null && !filteredQuery.getFilters().isEmpty()) {
338    // Since we can't modify the Hibernate query statement at this point we need to create a new one to apply
339    // the query filter. This comes with a performance cost, we could fix it by handling named queries ourselves
340    // and not delegate them to Hibernate. This way we would always get a statement that we can transform before
341    // the execution.
342  1707 boolean isNative = hQuery instanceof SQLQuery;
343  1707 String language = isNative ? "sql" : Query.HQL;
344  1707 final String statement = hQuery.getQueryString();
345   
346    // Run filters
347  1706 filteredQuery = filterQuery(new WrappingQuery(filteredQuery) {
 
348  3413 toggle @Override
349    public String getStatement()
350    {
351  3413 return statement;
352    }
353    }, language);
354   
355  1707 if (isNative) {
356  1680 hQuery = session.createSQLQuery(filteredQuery.getStatement());
357    // Copy the information about the return column types, if possible.
358  1680 NamedSQLQueryDefinition definition = (NamedSQLQueryDefinition) this.sessionFactory.getConfiguration()
359    .getNamedSQLQueries().get(query.getStatement());
360  1680 if (!StringUtils.isEmpty(definition.getResultSetRef())) {
361  1680 ((SQLQuery) hQuery).setResultSetMapping(definition.getResultSetRef());
362    }
363    } else {
364  27 hQuery = session.createQuery(filteredQuery.getStatement());
365    }
366    }
367  3240 populateParameters(hQuery, filteredQuery);
368  3241 return hQuery;
369    }
370   
371    /**
372    * @param hquery query to populate parameters
373    * @param query query from to populate.
374    */
 
375  136875 toggle protected void populateParameters(org.hibernate.Query hquery, Query query)
376    {
377  136875 if (query.getOffset() > 0) {
378  852 hquery.setFirstResult(query.getOffset());
379    }
380  136880 if (query.getLimit() > 0) {
381  15462 hquery.setMaxResults(query.getLimit());
382    }
383  136882 for (Entry<String, Object> e : query.getNamedParameters().entrySet()) {
384  199320 setNamedParameter(hquery, e.getKey(), e.getValue());
385    }
386  136883 Map<Integer, Object> positionalParameters = query.getPositionalParameters();
387  136878 if (positionalParameters.size() > 0) {
388  10675 int start = Collections.min(positionalParameters.keySet());
389  10675 if (start == 0) {
390    // jdbc-style positional parameters. "?"
391  10675 for (Entry<Integer, Object> e : positionalParameters.entrySet()) {
392  12439 hquery.setParameter(e.getKey(), e.getValue());
393    }
394    } else {
395    // jpql-style. "?index"
396  0 for (Entry<Integer, Object> e : positionalParameters.entrySet()) {
397    // hack. hibernate assume "?1" is named parameter, so use string "1".
398  0 setNamedParameter(hquery, String.valueOf(e.getKey()), e.getValue());
399    }
400    }
401    }
402    }
403   
404    /**
405    * Sets the value of the specified named parameter, taking into account the type of the given value.
406    *
407    * @param query the query to set the parameter for
408    * @param name the parameter name
409    * @param value the non-null parameter value
410    */
 
411  199325 toggle protected void setNamedParameter(org.hibernate.Query query, String name, Object value)
412    {
413  199336 if (value instanceof Collection) {
414  3051 query.setParameterList(name, (Collection<?>) value);
415  196285 } else if (value.getClass().isArray()) {
416  1 query.setParameterList(name, (Object[]) value);
417    } else {
418  196278 query.setParameter(name, value);
419    }
420    }
421   
422    /**
423    * @return Store component
424    */
 
425  136958 toggle protected XWikiHibernateStore getStore()
426    {
427  136958 return getContext().getWiki().getHibernateStore();
428    }
429   
430    /**
431    * @return XWiki Context
432    */
 
433  652246 toggle protected XWikiContext getContext()
434    {
435  652260 return (XWikiContext) this.execution.getContext().getProperty(XWikiContext.EXECUTIONCONTEXT_KEY);
436    }
437    }