1. Project Clover database Tue Dec 20 2016 21:24:09 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

44
83
13
1
335
217
43
0.52
6.38
13
3.31

Classes

Class Line # Actions
HqlQueryExecutor 70 83 0% 43 11
0.9214285692.1%
 

Contributing tests

This file is covered by 82 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.Singleton;
33   
34    import org.apache.commons.lang.StringUtils;
35    import org.hibernate.SQLQuery;
36    import org.hibernate.Session;
37    import org.hibernate.cfg.Configuration;
38    import org.hibernate.engine.NamedQueryDefinition;
39    import org.hibernate.engine.NamedSQLQueryDefinition;
40    import org.xwiki.component.annotation.Component;
41    import org.xwiki.component.phase.Initializable;
42    import org.xwiki.component.phase.InitializationException;
43    import org.xwiki.context.Execution;
44    import org.xwiki.job.event.status.JobProgressManager;
45    import org.xwiki.query.Query;
46    import org.xwiki.query.QueryException;
47    import org.xwiki.query.QueryExecutor;
48    import org.xwiki.query.QueryFilter;
49    import org.xwiki.query.SecureQuery;
50    import org.xwiki.security.authorization.ContextualAuthorizationManager;
51    import org.xwiki.security.authorization.Right;
52   
53    import com.xpn.xwiki.XWikiContext;
54    import com.xpn.xwiki.XWikiException;
55    import com.xpn.xwiki.internal.store.hibernate.query.HqlQueryUtils;
56    import com.xpn.xwiki.store.XWikiHibernateBaseStore.HibernateCallback;
57    import com.xpn.xwiki.store.XWikiHibernateStore;
58    import com.xpn.xwiki.store.hibernate.HibernateSessionFactory;
59    import com.xpn.xwiki.util.Util;
60   
61    /**
62    * QueryExecutor implementation for Hibernate Store.
63    *
64    * @version $Id: 7aa34ab0aceb5e1a2558137d1233b7fb459be9c9 $
65    * @since 1.6M1
66    */
67    @Component
68    @Named("hql")
69    @Singleton
 
70    public class HqlQueryExecutor implements QueryExecutor, Initializable
71    {
72    /**
73    * Path to Hibernate mapping with named queries. Configured via component manager.
74    */
75    private static final String MAPPING_PATH = "queries.hbm.xml";
76   
77    /**
78    * Session factory needed for register named queries mapping.
79    */
80    @Inject
81    private HibernateSessionFactory sessionFactory;
82   
83    /**
84    * Used for access to XWikiContext.
85    */
86    @Inject
87    private Execution execution;
88   
89    @Inject
90    private ContextualAuthorizationManager authorization;
91   
92    @Inject
93    private JobProgressManager progress;
94   
95    private volatile Set<String> allowedNamedQueries;
96   
 
97  206 toggle @Override
98    public void initialize() throws InitializationException
99    {
100  206 Configuration configuration = this.sessionFactory.getConfiguration();
101   
102  206 configuration.addInputStream(Util.getResourceAsStream(MAPPING_PATH));
103    }
104   
 
105  1 toggle private Set<String> getAllowedNamedQueries()
106    {
107  1 if (this.allowedNamedQueries == null) {
108  1 synchronized (this) {
109  1 if (this.allowedNamedQueries == null) {
110  1 this.allowedNamedQueries = new HashSet<>();
111   
112  1 Configuration configuration = this.sessionFactory.getConfiguration();
113   
114    // Gather the list of allowed named queries
115  1 Map<String, NamedQueryDefinition> namedQueries = configuration.getNamedQueries();
116  1 for (Map.Entry<String, NamedQueryDefinition> query : namedQueries.entrySet()) {
117  0 if (HqlQueryUtils.isSafe(query.getValue().getQuery())) {
118  0 this.allowedNamedQueries.add(query.getKey());
119    }
120    }
121    }
122    }
123    }
124   
125  1 return this.allowedNamedQueries;
126    }
127   
128    /**
129    * @param statementString the statement to evaluate
130    * @return true if the select is allowed for user without PR
131    */
 
132  27 toggle protected static boolean isSafeSelect(String statementString)
133    {
134  27 return HqlQueryUtils.isShortFormStatement(statementString) || HqlQueryUtils.isSafe(statementString);
135    }
136   
 
137  11180 toggle protected void checkAllowed(final Query query) throws QueryException
138    {
139  11183 if (query instanceof SecureQuery && ((SecureQuery) query).isCurrentAuthorChecked()) {
140  283 if (!this.authorization.hasAccess(Right.PROGRAM)) {
141  28 if (query.isNamed() && !getAllowedNamedQueries().contains(query.getStatement())) {
142  1 throw new QueryException("Named queries requires programming right", query, null);
143    }
144   
145  27 if (!isSafeSelect(query.getStatement())) {
146  3 throw new QueryException("The query requires programming right", query, null);
147    }
148    }
149    }
150    }
151   
 
152  11184 toggle @Override
153    public <T> List<T> execute(final Query query) throws QueryException
154    {
155    // Make sure the query is allowed in the current context
156  11184 checkAllowed(query);
157   
158  11176 String oldDatabase = getContext().getWikiId();
159  11181 try {
160  11181 this.progress.startStep(query, "query.hql.progress.execute", "Execute HQL query [{}]", query);
161   
162  11179 if (query.getWiki() != null) {
163  5799 getContext().setWikiId(query.getWiki());
164    }
165  11177 return getStore().executeRead(getContext(), new HibernateCallback<List<T>>()
166    {
 
167  11174 toggle @SuppressWarnings("unchecked")
168    @Override
169    public List<T> doInHibernate(Session session)
170    {
171  11172 org.hibernate.Query hquery = createHibernateQuery(session, query);
172  11173 populateParameters(hquery, query);
173   
174  11171 List<T> results = hquery.list();
175  11170 if (query.getFilters() != null && !query.getFilters().isEmpty()) {
176  1084 for (QueryFilter filter : query.getFilters()) {
177  1319 results = filter.filterResults(results);
178    }
179    }
180  11171 return results;
181    }
182    });
183    } catch (XWikiException e) {
184  1 throw new QueryException("Exception while executing query", query, e);
185    } finally {
186  11177 getContext().setWikiId(oldDatabase);
187   
188  11181 this.progress.endStep(query);
189    }
190    }
191   
192    /**
193    * Append the required select clause to HQL short query statements. Short statements are the only way for users
194    * without programming rights to perform queries. Such statements can be for example:
195    * <ul>
196    * <li>{@code , BaseObject obj where doc.fullName=obj.name and obj.className='XWiki.MyClass'}</li>
197    * <li>{@code where doc.creationDate > '2008-01-01'}</li>
198    * </ul>
199    *
200    * @param statement the statement to complete if required.
201    * @return the complete statement if it had to be completed, the original one otherwise.
202    */
 
203  10888 toggle protected String completeShortFormStatement(String statement)
204    {
205  10887 String lcStatement = statement.toLowerCase().trim();
206  10886 if (lcStatement.isEmpty() || lcStatement.startsWith(",") || lcStatement.startsWith("where ")
207    || lcStatement.startsWith("order by ")) {
208  4296 return "select doc.fullName from XWikiDocument doc " + statement.trim();
209    }
210   
211  6592 return statement;
212    }
213   
214    /**
215    * @param session hibernate session
216    * @param query Query object
217    * @return hibernate query
218    */
 
219  11173 toggle protected org.hibernate.Query createHibernateQuery(Session session, Query query)
220    {
221  11174 org.hibernate.Query hquery;
222   
223  11175 if (!query.isNamed()) {
224    // handle short queries
225  10882 String statement = completeShortFormStatement(query.getStatement());
226   
227    // Handle query filters
228  10883 if (query.getFilters() != null) {
229  10879 for (QueryFilter filter : query.getFilters()) {
230  1227 statement = filter.filterStatement(statement, Query.HQL);
231    }
232    }
233  10880 hquery = session.createQuery(statement);
234    } else {
235  291 hquery = createNamedHibernateQuery(session, query);
236    }
237   
238  11174 return hquery;
239    }
240   
 
241  291 toggle private org.hibernate.Query createNamedHibernateQuery(Session session, Query query)
242    {
243  291 org.hibernate.Query hQuery = session.getNamedQuery(query.getStatement());
244  291 if (query.getFilters() != null && !query.getFilters().isEmpty()) {
245    // Since we can't modify the Hibernate query statement at this point we need to create a new one to apply
246    // the query filter. This comes with a performance cost, we could fix it by handling named queries ourselves
247    // and not delegate them to Hibernate. This way we would always get a statement that we can transform before
248    // the execution.
249  71 boolean isNative = hQuery instanceof SQLQuery;
250  71 String language = isNative ? "sql" : Query.HQL;
251  71 String statement = hQuery.getQueryString();
252  71 for (QueryFilter filter : query.getFilters()) {
253  93 statement = filter.filterStatement(statement, language);
254    }
255  71 if (isNative) {
256  12 hQuery = session.createSQLQuery(statement);
257    // Copy the information about the return column types, if possible.
258  12 NamedSQLQueryDefinition definition = (NamedSQLQueryDefinition) this.sessionFactory.getConfiguration()
259    .getNamedSQLQueries().get(query.getStatement());
260  12 if (!StringUtils.isEmpty(definition.getResultSetRef())) {
261  12 ((SQLQuery) hQuery).setResultSetMapping(definition.getResultSetRef());
262    }
263    } else {
264  59 hQuery = session.createQuery(statement);
265    }
266    }
267  291 return hQuery;
268    }
269   
270    /**
271    * @param hquery query to populate parameters
272    * @param query query from to populate.
273    */
 
274  11173 toggle protected void populateParameters(org.hibernate.Query hquery, Query query)
275    {
276  11174 if (query.getOffset() > 0) {
277  70 hquery.setFirstResult(query.getOffset());
278    }
279  11173 if (query.getLimit() > 0) {
280  393 hquery.setMaxResults(query.getLimit());
281    }
282  11172 for (Entry<String, Object> e : query.getNamedParameters().entrySet()) {
283  14024 setNamedParameter(hquery, e.getKey(), e.getValue());
284    }
285  11172 if (query.getPositionalParameters().size() > 0) {
286  151 int start = Collections.min(query.getPositionalParameters().keySet());
287  151 if (start == 0) {
288    // jdbc-style positional parameters. "?"
289  151 for (Entry<Integer, Object> e : query.getPositionalParameters().entrySet()) {
290  325 hquery.setParameter(e.getKey(), e.getValue());
291    }
292    } else {
293    // jpql-style. "?index"
294  0 for (Entry<Integer, Object> e : query.getPositionalParameters().entrySet()) {
295    // hack. hibernate assume "?1" is named parameter, so use string "1".
296  0 setNamedParameter(hquery, String.valueOf(e.getKey()), e.getValue());
297    }
298    }
299    }
300    }
301   
302    /**
303    * Sets the value of the specified named parameter, taking into account the type of the given value.
304    *
305    * @param query the query to set the parameter for
306    * @param name the parameter name
307    * @param value the non-null parameter value
308    */
 
309  14027 toggle protected void setNamedParameter(org.hibernate.Query query, String name, Object value)
310    {
311  14031 if (value instanceof Collection) {
312  2 query.setParameterList(name, (Collection<?>) value);
313  14027 } else if (value.getClass().isArray()) {
314  1 query.setParameterList(name, (Object[]) value);
315    } else {
316  14026 query.setParameter(name, value);
317    }
318    }
319   
320    /**
321    * @return Store component
322    */
 
323  11178 toggle protected XWikiHibernateStore getStore()
324    {
325  11177 return getContext().getWiki().getHibernateStore();
326    }
327   
328    /**
329    * @return XWiki Context
330    */
 
331  50509 toggle protected XWikiContext getContext()
332    {
333  50514 return (XWikiContext) this.execution.getContext().getProperty(XWikiContext.EXECUTIONCONTEXT_KEY);
334    }
335    }