001 /*
002 * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003 * Copyright (C) 2011 NightLabs Consulting GmbH
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018 package org.cumulus4j.store.query;
019
020 import java.util.Collection;
021 import java.util.Deque;
022 import java.util.HashMap;
023 import java.util.HashSet;
024 import java.util.Iterator;
025 import java.util.LinkedList;
026 import java.util.List;
027 import java.util.Map;
028 import java.util.Set;
029
030 import javax.jdo.PersistenceManager;
031
032 import org.cumulus4j.store.Cumulus4jStoreManager;
033 import org.cumulus4j.store.EncryptionHandler;
034 import org.cumulus4j.store.PersistenceManagerConnection;
035 import org.cumulus4j.store.crypto.CryptoContext;
036 import org.cumulus4j.store.model.ClassMeta;
037 import org.cumulus4j.store.model.DataEntry;
038 import org.cumulus4j.store.query.eval.AbstractExpressionEvaluator;
039 import org.cumulus4j.store.query.eval.AndExpressionEvaluator;
040 import org.cumulus4j.store.query.eval.ComparisonExpressionEvaluator;
041 import org.cumulus4j.store.query.eval.InvokeExpressionEvaluator;
042 import org.cumulus4j.store.query.eval.LiteralEvaluator;
043 import org.cumulus4j.store.query.eval.NotExpressionEvaluator;
044 import org.cumulus4j.store.query.eval.OrExpressionEvaluator;
045 import org.cumulus4j.store.query.eval.ParameterExpressionEvaluator;
046 import org.cumulus4j.store.query.eval.PrimaryExpressionEvaluator;
047 import org.cumulus4j.store.query.eval.ResultDescriptor;
048 import org.cumulus4j.store.query.eval.SubqueryExpressionEvaluator;
049 import org.cumulus4j.store.query.eval.VariableExpressionEvaluator;
050 import org.datanucleus.ClassLoaderResolver;
051 import org.datanucleus.identity.IdentityUtils;
052 import org.datanucleus.metadata.AbstractClassMetaData;
053 import org.datanucleus.query.QueryUtils;
054 import org.datanucleus.query.compiler.QueryCompilation;
055 import org.datanucleus.query.expression.DyadicExpression;
056 import org.datanucleus.query.expression.Expression;
057 import org.datanucleus.query.expression.InvokeExpression;
058 import org.datanucleus.query.expression.Literal;
059 import org.datanucleus.query.expression.ParameterExpression;
060 import org.datanucleus.query.expression.PrimaryExpression;
061 import org.datanucleus.query.expression.SubqueryExpression;
062 import org.datanucleus.query.expression.VariableExpression;
063 import org.datanucleus.query.symbol.Symbol;
064 import org.datanucleus.store.ExecutionContext;
065 import org.datanucleus.store.query.Query;
066
067 /**
068 * API-agnostic query implementation. An instance of this class performs the actual query.
069 * It is used by both APIs, JDO and JPA.
070 *
071 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
072 */
073 public abstract class QueryEvaluator
074 {
075 /** Name under which any set of results are stored in the state map. Used for aggregation. */
076 public static final String RESULTS_SET = "DATANUCLEUS_RESULTS_SET";
077
078 private final String language;
079
080 private String candidateAlias = "this";
081
082 /** Underlying "string-based" query. */
083 private Query query;
084
085 /** Compilation of the underlying query, that we are evaluating. */
086 private QueryCompilation compilation;
087
088 /** Map of input parameter values, keyed by the parameter name. */
089 private Map<String, Object> parameterValues;
090
091 /** Positional parameter that we are up to (-1 implies not being used). */
092 Map<Integer, Symbol> paramSymbolByPosition = null;
093
094 /** Map of state symbols for the query evaluation. */
095 private Map<String, Object> state;
096
097 private ClassLoaderResolver clr;
098
099 private ExecutionContext ec;
100
101 private Cumulus4jStoreManager storeManager;
102
103 private CryptoContext cryptoContext;
104
105 private PersistenceManagerConnection pmConn;
106
107 private EncryptionHandler encryptionHandler;
108
109 private boolean complete = true;
110
111 /**
112 * @param language Query language (JDOQL, JPQL, etc)
113 * @param compilation generic compilation
114 * @param parameterValues Input values for the params
115 * @param clr ClassLoader resolver
116 * @param pmConn our <b>backend</b>-<code>PersistenceManager</code> connection(s).
117 */
118 public QueryEvaluator(
119 String language, Query query, QueryCompilation compilation, Map<String, Object> parameterValues,
120 ClassLoaderResolver clr, PersistenceManagerConnection pmConn)
121 {
122 this.language = language;
123 this.query = query;
124 this.compilation = compilation;
125 this.parameterValues = parameterValues;
126 this.clr = clr;
127 this.ec = query.getExecutionContext();
128 this.storeManager = (Cumulus4jStoreManager) query.getStoreManager();
129 this.pmConn = pmConn;
130 this.cryptoContext = new CryptoContext(storeManager.getEncryptionCoordinateSetManager(), ec, pmConn);
131 this.encryptionHandler = storeManager.getEncryptionHandler();
132
133 this.candidateAlias = (compilation.getCandidateAlias() != null ? compilation.getCandidateAlias() : this.candidateAlias);
134
135 state = new HashMap<String, Object>();
136 state.put(this.candidateAlias, query.getCandidateClass());
137
138 if (parameterValues != null && !parameterValues.isEmpty()) {
139 Object paramKey = parameterValues.keySet().iterator().next();
140 if (paramKey instanceof Integer) {
141 paramSymbolByPosition = new HashMap<Integer, Symbol>();
142 }
143 }
144 }
145
146 public boolean isComplete() {
147 return complete;
148 }
149
150 public void setIncomplete() {
151 this.complete = false;
152 }
153
154 public String getLanguage() {
155 return language;
156 }
157
158 public String getCandidateAlias() {
159 return candidateAlias;
160 }
161
162 public Query getQuery() {
163 return query;
164 }
165
166 public QueryCompilation getCompilation() {
167 return compilation;
168 }
169
170 public Map<String, Object> getParameterValues() {
171 return parameterValues;
172 }
173
174 public Map<String, Object> getState() {
175 return state;
176 }
177
178 public ClassLoaderResolver getClassLoaderResolver() {
179 return clr;
180 }
181
182 public ExecutionContext getExecutionContext() {
183 return ec;
184 }
185
186 public Cumulus4jStoreManager getStoreManager() {
187 return storeManager;
188 }
189
190 public PersistenceManagerConnection getPersistenceManagerConnection() {
191 return pmConn;
192 }
193
194 public PersistenceManager getPersistenceManagerForData() {
195 return pmConn.getDataPM();
196 }
197
198 public PersistenceManager getPersistenceManagerForIndex() {
199 return pmConn.getIndexPM();
200 }
201
202 public EncryptionHandler getEncryptionHandler() {
203 return encryptionHandler;
204 }
205
206 private Deque<ResultDescriptor> resultDescriptors = new LinkedList<ResultDescriptor>();
207
208 /**
209 * Push a {@link ResultDescriptor} onto the stack.
210 * @param resultDescriptor the descriptor to be pushed.
211 */
212 public void pushResultDescriptor(ResultDescriptor resultDescriptor)
213 {
214 resultDescriptors.push(resultDescriptor);
215 }
216
217 /**
218 * Pop a {@link ResultDescriptor} from the stack.
219 * @return the popped descriptor (which is the last one pushed).
220 */
221 public ResultDescriptor popResultDescriptor()
222 {
223 return resultDescriptors.pop();
224 }
225
226 /**
227 * Get a <code>Symbol</code>'s {@link Symbol#getValueType() valueType} by taking {@link ResultDescriptor}s into account.
228 * Delegates to {@link #getValueType(Symbol, boolean)} with <code>throwExceptionIfNotFound == true</code>.
229 *
230 * @param symbol the symbol whose {@link Symbol#getValueType() valueType} should be resolved.
231 * @return the type - never <code>null</code>.
232 * @see #getValueType(Symbol, boolean)
233 */
234 public Class<?> getValueType(Symbol symbol)
235 {
236 return getValueType(symbol, true);
237 }
238
239 /**
240 * <p>
241 * Get a <code>Symbol</code>'s {@link Symbol#getValueType() valueType} by taking {@link ResultDescriptor}s into account.
242 * </p>
243 * <p>
244 * This method (or alternatively {@link #getValueType(Symbol)}) should always be used instead of directly
245 * accessing {@link Symbol#getValueType()}!!! This allows for implicit variables (which are not declared).
246 * </p>
247 * <p>
248 * This method first checks, if {@link Symbol#getValueType()} returns a value and if so, returns it. Otherwise
249 * it searches the stack of {@link ResultDescriptor}s (maintained via {@link #pushResultDescriptor(ResultDescriptor)}
250 * and {@link #popResultDescriptor()}) and returns the first found {@link ResultDescriptor#getResultType()}.
251 * </p>
252 *
253 * @param symbol the symbol whose {@link Symbol#getValueType() valueType} should be resolved.
254 * @param throwExceptionIfNotFound whether to throw an {@link IllegalStateException} [exception type might be changed without notice!],
255 * if the type cannot be resolved. If <code>false</code> this method returns <code>null</code> instead.
256 * @return the type or <code>null</code>, if not resolvable and <code>throwExceptionIfNotFound == false</code>.
257 * @see #getValueType(Symbol)
258 * @see #pushResultDescriptor(ResultDescriptor)
259 * @see #popResultDescriptor()
260 */
261 public Class<?> getValueType(Symbol symbol, boolean throwExceptionIfNotFound)
262 {
263 if (symbol.getValueType() != null)
264 return symbol.getValueType();
265
266 for (ResultDescriptor resultDescriptor : resultDescriptors) {
267 if (symbol.equals(resultDescriptor.getSymbol()))
268 return resultDescriptor.getResultType();
269 }
270
271 if (symbol.getType() == Symbol.PARAMETER) {
272 // Cater for implicit parameters where the generic compilation doesn't have the type
273 if (paramSymbolByPosition != null) {
274 // Positional parameters
275 Iterator<Map.Entry<Integer, Symbol>> paramIter = paramSymbolByPosition.entrySet().iterator();
276 while (paramIter.hasNext()) {
277 Map.Entry<Integer, Symbol> entry = paramIter.next();
278 if (entry.getValue() == symbol) {
279 return parameterValues.get(entry.getKey()).getClass();
280 }
281 }
282
283 Integer nextPos = paramSymbolByPosition.size();
284 Object value = parameterValues.get(nextPos);
285 paramSymbolByPosition.put(nextPos, symbol);
286 return value.getClass();
287 }
288 else {
289 if (parameterValues.containsKey(symbol.getQualifiedName())) {
290 return parameterValues.get(symbol.getQualifiedName()).getClass();
291 }
292 }
293 }
294 if (throwExceptionIfNotFound)
295 throw new IllegalStateException("Could not determine the resultType of symbol \"" + symbol + "\"! If this is a variable, you might want to declare it.");
296
297 return null;
298 }
299
300 protected abstract Collection<Object> evaluateSubquery(
301 Query subquery, QueryCompilation compilation, Object outerCandidate
302 );
303
304 public List<Object> execute()
305 {
306 Class<?> candidateClass = query.getCandidateClass();
307 boolean withSubclasses = query.isSubclasses();
308 Set<ClassMeta> candidateClassMetas = QueryHelper.getCandidateClassMetas(storeManager, ec, candidateClass, withSubclasses);
309
310 // TODO I copied this from the JavaQueryEvaluator, but I'm not sure, whether we need this. Need to talk with Andy. Marco.
311 // ...or analyse it ourselves (step through)...
312 String[] subqueryAliases = compilation.getSubqueryAliases();
313 if (subqueryAliases != null) {
314 for (int i=0; i<subqueryAliases.length; ++i) {
315 // Evaluate subquery first
316 Query subquery = query.getSubqueryForVariable(subqueryAliases[i]).getQuery();
317 QueryCompilation subqueryCompilation = compilation.getCompilationForSubquery(subqueryAliases[i]);
318
319 Collection<Object> subqueryResult = evaluateSubquery(subquery, subqueryCompilation, null);
320
321 if (QueryUtils.queryReturnsSingleRow(subquery)) {
322 Iterator<Object> subqueryIterator = subqueryResult.iterator();
323 if (!subqueryIterator.hasNext()) // TODO simply use null in this case?!????
324 throw new IllegalStateException("Subquery is expected to return a single row, but it returned an empty collection!");
325
326 state.put(subqueryAliases[i], subqueryIterator.next());
327
328 if (subqueryIterator.hasNext())
329 throw new IllegalStateException("Subquery is expected to return only a single row, but it returned more than one!");
330 }
331 else
332 state.put(subqueryAliases[i], subqueryResult);
333 }
334 }
335
336 if (compilation.getExprFilter() == null) {
337 // No filter - we want all that match the candidate classes.
338 return QueryHelper.getAllPersistentObjectsForCandidateClasses(getPersistenceManagerForData(), ec, candidateClassMetas);
339 }
340 else {
341 expressionEvaluator = createExpressionEvaluatorTree(compilation.getExprFilter());
342 Symbol resultSymbol = getCompilation().getSymbolTable().getSymbol(getCandidateAlias());
343 if (resultSymbol == null)
344 throw new IllegalStateException("getCompilation().getSymbolTable().getSymbol(getCandidateAlias()) returned null! getCandidateAlias()==\"" + getCandidateAlias() + "\"");
345
346 return expressionEvaluator.queryResultObjects(new ResultDescriptor(resultSymbol, null));
347 }
348 }
349
350 private AbstractExpressionEvaluator<?> expressionEvaluator;
351
352 public AbstractExpressionEvaluator<?> getExpressionEvaluator() {
353 return expressionEvaluator;
354 }
355
356 private AbstractExpressionEvaluator<?> createExpressionEvaluatorTree(Expression expression)
357 {
358 return createExpressionEvaluatorTreeRecursive(null, expression);
359 }
360
361 private AbstractExpressionEvaluator<?> createExpressionEvaluatorTreeRecursive(AbstractExpressionEvaluator<?> parent, Expression expression)
362 {
363 AbstractExpressionEvaluator<?> eval = createExpressionEvaluator(parent, expression);
364
365 if (expression.getLeft() != null) {
366 AbstractExpressionEvaluator<?> childEval = createExpressionEvaluatorTreeRecursive(eval, expression.getLeft());
367 eval.setLeft(childEval);
368 }
369
370 if (expression.getRight() != null) {
371 AbstractExpressionEvaluator<?> childEval = createExpressionEvaluatorTreeRecursive(eval, expression.getRight());
372 eval.setRight(childEval);
373 }
374
375 return eval;
376 }
377
378 private AbstractExpressionEvaluator<?> createExpressionEvaluator(
379 AbstractExpressionEvaluator<?> parent,
380 Expression expr
381 )
382 {
383 if (expr instanceof DyadicExpression) {
384 DyadicExpression expression = (DyadicExpression) expr;
385 if (
386 Expression.OP_EQ == expression.getOperator() ||
387 Expression.OP_NOTEQ == expression.getOperator() ||
388 Expression.OP_LT == expression.getOperator() ||
389 Expression.OP_LTEQ == expression.getOperator() ||
390 Expression.OP_GT == expression.getOperator() ||
391 Expression.OP_GTEQ == expression.getOperator()
392 )
393 return new ComparisonExpressionEvaluator(this, parent, expression);
394 else if (Expression.OP_AND == expression.getOperator())
395 return new AndExpressionEvaluator(this, parent, expression);
396 else if (Expression.OP_OR == expression.getOperator())
397 return new OrExpressionEvaluator(this, parent, expression);
398 else if (Expression.OP_NOT == expression.getOperator())
399 return new NotExpressionEvaluator(this, parent, expression);
400 else
401 throw new UnsupportedOperationException("Unsupported operator for DyadicExpression: " + expr);
402 }
403
404 if (expr instanceof PrimaryExpression)
405 return new PrimaryExpressionEvaluator(this, parent, (PrimaryExpression) expr);
406
407 if (expr instanceof ParameterExpression)
408 return new ParameterExpressionEvaluator(this, parent, (ParameterExpression) expr);
409
410 if (expr instanceof Literal)
411 return new LiteralEvaluator(this, parent, (Literal) expr);
412
413 if (expr instanceof InvokeExpression)
414 return new InvokeExpressionEvaluator(this, parent, (InvokeExpression) expr);
415
416 if (expr instanceof VariableExpression)
417 return new VariableExpressionEvaluator(this, parent, (VariableExpression) expr);
418
419 if (expr instanceof SubqueryExpression)
420 return new SubqueryExpressionEvaluator(this, parent, (SubqueryExpression) expr);
421
422 throw new UnsupportedOperationException("Don't know what to do with this expression: " + expr);
423 }
424
425 public Object getObjectForDataEntry(DataEntry dataEntry)
426 {
427 return getObjectForClassMetaAndObjectIDString(dataEntry.getClassMeta(), dataEntry.getObjectID());
428 }
429
430 public Object getObjectForClassMetaAndObjectIDString(ClassMeta classMeta, String objectIDString)
431 {
432 AbstractClassMetaData cmd = classMeta.getDataNucleusClassMetaData(ec);
433 return IdentityUtils.getObjectFromIdString(objectIDString, cmd, ec, true);
434 }
435
436 public Set<Long> getAllDataEntryIDsForCandidateClasses(Set<ClassMeta> candidateClassMetas)
437 {
438 javax.jdo.Query q = getPersistenceManagerForData().newQuery(DataEntry.class);
439 q.setResult("this.dataEntryID");
440
441 Object queryParam;
442 if (candidateClassMetas.size() == 1) {
443 q.setFilter("this.classMeta == :classMeta");
444 queryParam = candidateClassMetas.iterator().next();
445 }
446 else {
447 q.setFilter(":classMetas.contains(this.classMeta)");
448 queryParam = candidateClassMetas;
449 }
450
451 @SuppressWarnings("unchecked")
452 Collection<Long> allDataEntryIDs = (Collection<Long>) q.execute(queryParam);
453 Set<Long> result = new HashSet<Long>(allDataEntryIDs);
454 q.closeAll();
455 return result;
456 }
457
458 public CryptoContext getCryptoContext() {
459 return cryptoContext;
460 }
461 }