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 }