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.eval; 019 020 import java.util.Collection; 021 import java.util.Collections; 022 import java.util.HashMap; 023 import java.util.HashSet; 024 import java.util.List; 025 import java.util.Map; 026 import java.util.Set; 027 028 import org.cumulus4j.store.model.ClassMeta; 029 import org.cumulus4j.store.model.DataEntry; 030 import org.cumulus4j.store.model.DataEntryDAO; 031 import org.cumulus4j.store.model.EmbeddedClassMeta; 032 import org.cumulus4j.store.model.FieldMeta; 033 import org.cumulus4j.store.model.FieldMetaRole; 034 import org.cumulus4j.store.model.IndexEntry; 035 import org.cumulus4j.store.model.IndexEntryFactory; 036 import org.cumulus4j.store.model.IndexEntryObjectRelationHelper; 037 import org.cumulus4j.store.model.IndexValue; 038 import org.cumulus4j.store.model.ObjectContainer; 039 import org.cumulus4j.store.query.QueryEvaluator; 040 import org.cumulus4j.store.query.QueryHelper; 041 import org.cumulus4j.store.query.method.MethodEvaluator; 042 import org.datanucleus.ClassLoaderResolver; 043 import org.datanucleus.metadata.AbstractMemberMetaData; 044 import org.datanucleus.plugin.ConfigurationElement; 045 import org.datanucleus.query.QueryUtils; 046 import org.datanucleus.query.expression.Expression; 047 import org.datanucleus.query.expression.Expression.Operator; 048 import org.datanucleus.query.expression.InvokeExpression; 049 import org.datanucleus.query.expression.Literal; 050 import org.datanucleus.query.expression.ParameterExpression; 051 import org.datanucleus.query.expression.PrimaryExpression; 052 import org.datanucleus.query.expression.VariableExpression; 053 import org.datanucleus.store.StoreManager; 054 import org.slf4j.Logger; 055 import org.slf4j.LoggerFactory; 056 057 /** 058 * Series of helper methods for processing expressions. 059 */ 060 public class ExpressionHelper 061 { 062 private static Map<String, Class<? extends MethodEvaluator>> method2EvaluatorClass = new HashMap<String, Class<? extends MethodEvaluator>>(); 063 064 /** 065 * Accessor for the evaluator object for use of method xxx(...) of class Yyy in queries. 066 * @param storeMgr Store Manager 067 * @param clr ClassLoader resolver 068 * @param clsName The class on which to invoke the method 069 * @param method The method to call on the class 070 * @return The MethodEvaluator 071 */ 072 @SuppressWarnings("unchecked") 073 public static MethodEvaluator createMethodEvaluatorForMethodOfClass(StoreManager storeMgr, ClassLoaderResolver clr, 074 String clsName, String method) { 075 076 String key = clsName + "." + method; 077 078 MethodEvaluator eval = null; 079 Class<? extends MethodEvaluator> evaluatorCls = method2EvaluatorClass.get(key); 080 if (evaluatorCls == null) { 081 ConfigurationElement elem = 082 storeMgr.getNucleusContext().getPluginManager().getConfigurationElementForExtension( 083 "org.cumulus4j.store.query_method", new String[]{"class", "method"}, new String[]{clsName, method}); 084 if (elem == null) { 085 throw new UnsupportedOperationException("Invocation of method \""+method+"\" on object of type \""+clsName+"\" is not supported"); 086 } 087 088 String evaluatorClassName = elem.getAttribute("evaluator"); 089 evaluatorCls = clr.classForName(evaluatorClassName); 090 try { 091 eval = evaluatorCls.newInstance(); 092 } catch (Exception e) { 093 throw new UnsupportedOperationException("Attempt to instantiate an evaluator for " + key + " failed: " + e, e); 094 } 095 096 // Cache the method for later use 097 method2EvaluatorClass.put(key, evaluatorCls); 098 } 099 100 if (eval == null) { 101 try { 102 eval = evaluatorCls.newInstance(); 103 } catch (Exception e) { 104 throw new UnsupportedOperationException("Attempt to instantiate an evaluator for " + key + " failed: " + e, e); 105 } 106 } 107 108 return eval; 109 } 110 111 /** 112 * Method to evaluate the arguments for passing in to a method invocation. 113 * @param queryEval The QueryEvaluator 114 * @param expr The invoke expression 115 * @return The argument(s) 116 */ 117 public static Object[] getEvaluatedInvokeArguments(QueryEvaluator queryEval, InvokeExpression expr) { 118 Object[] invokeArgs = new Object[expr.getArguments().size()]; 119 120 int i=0; 121 for (Expression argExpr : expr.getArguments()) { 122 if (argExpr instanceof Literal) 123 invokeArgs[i++] = ((Literal)argExpr).getLiteral(); 124 else if (argExpr instanceof ParameterExpression) 125 invokeArgs[i++] = QueryUtils.getValueForParameterExpression(queryEval.getParameterValues(), 126 (ParameterExpression)argExpr); 127 else 128 throw new UnsupportedOperationException("NYI"); 129 } 130 return invokeArgs; 131 } 132 133 /** 134 * Method to evaluate the argument for passing in to a method invocation. 135 * @param queryEval The QueryEvaluator 136 * @param expr The invoke expression 137 * @return The argument 138 */ 139 public static Object getEvaluatedInvokeArgument(QueryEvaluator queryEval, InvokeExpression expr) { 140 if (expr.getArguments().size() != 1) { 141 throw new UnsupportedOperationException("Invalid number of arguments to " + expr.getOperation()); 142 } 143 144 Object argExpr = expr.getArguments().get(0); 145 if (argExpr instanceof Literal) 146 return ((Literal)argExpr).getLiteral(); 147 else if (argExpr instanceof ParameterExpression) 148 return QueryUtils.getValueForParameterExpression(queryEval.getParameterValues(), (ParameterExpression)argExpr); 149 else 150 throw new UnsupportedOperationException("NYI"); 151 } 152 153 private static abstract class AbstractContainsResolver extends PrimaryExpressionResolver 154 { 155 protected FieldMetaRole role; 156 protected boolean negate; 157 158 protected Set<Long> negateIfNecessary(FieldMeta fieldMeta, Set<Long> positiveResult) 159 { 160 if (!negate) { 161 return positiveResult; 162 } 163 164 Class<?> candidateClass = executionContext.getClassLoaderResolver().classForName(fieldMeta.getClassMeta().getClassName()); 165 Set<ClassMeta> candidateClassMetas = QueryHelper.getCandidateClassMetas(queryEvaluator.getStoreManager(), 166 executionContext, candidateClass, true); 167 Set<Long> allDataEntryIDs = queryEvaluator.getAllDataEntryIDsForCandidateClasses(candidateClassMetas); 168 169 Set<Long> negativeResult = new HashSet<Long>(allDataEntryIDs.size() - positiveResult.size()); 170 for (Long dataEntryID : allDataEntryIDs) { 171 if (!positiveResult.contains(dataEntryID)) 172 negativeResult.add(dataEntryID); 173 } 174 return negativeResult; 175 } 176 177 public AbstractContainsResolver( 178 QueryEvaluator queryEvaluator, PrimaryExpression primaryExpression, 179 FieldMetaRole role, boolean negate 180 ) 181 { 182 super(queryEvaluator, primaryExpression); 183 this.role = role; 184 this.negate = negate; 185 186 if (role != FieldMetaRole.collectionElement && role != FieldMetaRole.mapKey && role != FieldMetaRole.mapValue) 187 throw new IllegalArgumentException("role == " + role); 188 } 189 190 @Override 191 protected final Set<Long> queryEnd(FieldMeta fieldMeta, ClassMeta classMeta) { 192 AbstractMemberMetaData mmd = fieldMeta.getDataNucleusMemberMetaData(executionContext); 193 FieldMeta subFieldMeta = fieldMeta.getSubFieldMeta(role); 194 195 boolean argumentIsPersistent; 196 Class<?> argumentType; 197 switch (role) { 198 case collectionElement: 199 argumentIsPersistent = mmd.getCollection().elementIsPersistent(); 200 argumentType = executionContext.getClassLoaderResolver().classForName(mmd.getCollection().getElementType()); 201 break; 202 case mapKey: 203 argumentIsPersistent = mmd.getMap().keyIsPersistent(); 204 argumentType = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getKeyType()); 205 break; 206 case mapValue: 207 argumentIsPersistent = mmd.getMap().valueIsPersistent(); 208 argumentType = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getValueType()); 209 break; 210 default: 211 throw new IllegalStateException("Unknown role: " + role); 212 } 213 214 return _queryEnd(fieldMeta, classMeta, mmd, subFieldMeta, argumentIsPersistent, argumentType); 215 } 216 217 protected abstract Set<Long> _queryEnd(FieldMeta fieldMeta, ClassMeta classMeta, AbstractMemberMetaData mmd, FieldMeta subFieldMeta, boolean argumentIsPersistent, Class<?> argumentType 218 ); 219 } 220 221 /** 222 * Resolve {@link Collection#contains(Object)} with the argument being a query variable. 223 * 224 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 225 */ 226 public static class ContainsVariableResolver extends AbstractContainsResolver 227 { 228 private VariableExpression variableExpr; 229 230 public ContainsVariableResolver( 231 QueryEvaluator queryEvaluator, PrimaryExpression primaryExpression, 232 FieldMetaRole role, VariableExpression variableExpr, boolean negate 233 ) 234 { 235 super(queryEvaluator, primaryExpression, role, negate); 236 this.variableExpr = variableExpr; 237 238 if (variableExpr == null) 239 throw new IllegalArgumentException("variableExpr == null"); 240 241 if (variableExpr.getSymbol() == null) 242 throw new IllegalArgumentException("variableExpr.getSymbol() == null"); 243 } 244 245 @Override 246 public Set<Long> _queryEnd(FieldMeta fieldMeta, ClassMeta classMeta, AbstractMemberMetaData mmd, 247 FieldMeta subFieldMeta, boolean argumentIsPersistent, Class<?> argumentType) 248 { 249 if (argumentIsPersistent || subFieldMeta.getMappedByFieldMeta(executionContext) != null) { 250 AbstractExpressionEvaluator<?> eval = queryEvaluator.getExpressionEvaluator(); 251 252 EmbeddedClassMeta embeddedClassMeta = subFieldMeta.getEmbeddedClassMeta(); 253 queryEvaluator.registerValueTypeEmbeddedClassMeta(variableExpr.getSymbol(), embeddedClassMeta); 254 255 Set<Long> valueDataEntryIDs = eval.queryResultDataEntryIDs( 256 new ResultDescriptor(variableExpr.getSymbol(), argumentType, subFieldMeta.getMappedByFieldMeta(executionContext), classMeta) 257 // new ResultDescriptor(variableExpr.getSymbol(), argumentType, null, classMeta) 258 ); 259 if (valueDataEntryIDs == null) 260 return null; 261 262 if (embeddedClassMeta != null) 263 return valueDataEntryIDs; 264 265 Set<Long> result = new HashSet<Long>(); 266 if (mmd.getMappedBy() != null) { 267 for (Long valueDataEntryID : valueDataEntryIDs) { 268 DataEntry valueDataEntry = new DataEntryDAO( 269 queryEvaluator.getPersistenceManagerForData(), cryptoContext.getKeyStoreRefID() 270 ).getDataEntry(valueDataEntryID); 271 ObjectContainer constantObjectContainer = queryEvaluator.getEncryptionHandler().decryptDataEntry(cryptoContext, valueDataEntry); 272 Object value = constantObjectContainer.getValue( 273 fieldMeta.getMappedByFieldMeta(executionContext).getFieldID() 274 ); 275 Long mappedByDataEntryID = (Long) value; 276 if (mappedByDataEntryID != null) 277 result.add(mappedByDataEntryID); 278 } 279 } 280 else { 281 for (Long valueDataEntryID : valueDataEntryIDs) { 282 // IndexEntry indexEntry = 283 // IndexEntryObjectRelationHelper.getIndexEntry(cryptoContext, queryEvaluator.getPersistenceManagerForIndex(), subFieldMeta, classMeta, valueDataEntryID); 284 // ClassMeta fieldOrElementTypeClassMeta = subFieldMeta.getFieldOrElementTypeClassMeta(executionContext); 285 List<IndexEntry> indexEntries = IndexEntryObjectRelationHelper.getIndexEntriesIncludingSubClasses( 286 cryptoContext, queryEvaluator.getPersistenceManagerForIndex(), subFieldMeta, fieldMeta.getClassMeta(), valueDataEntryID 287 ); 288 for (IndexEntry indexEntry : indexEntries) { 289 IndexValue indexValue = queryEvaluator.getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry); 290 result.addAll(indexValue.getDataEntryIDs()); 291 } 292 } 293 } 294 return negateIfNecessary(fieldMeta, result); 295 } 296 else { 297 AbstractExpressionEvaluator<?> eval = queryEvaluator.getExpressionEvaluator(); 298 Set<Long> result = eval.queryResultDataEntryIDs(new ResultDescriptor(variableExpr.getSymbol(), argumentType, subFieldMeta, classMeta)); 299 return negateIfNecessary(fieldMeta, result); 300 } 301 } 302 } 303 304 /** 305 * Resolve {@link Collection#contains(Object)} with the argument being a concrete value (a 'constant'). 306 * This concrete value is either a query parameter or a literal - i.e. no variable. 307 * 308 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 309 */ 310 public static class ContainsConstantResolver extends AbstractContainsResolver 311 { 312 private static Logger logger = LoggerFactory.getLogger(ContainsConstantResolver.class); 313 private Object constant; 314 315 public ContainsConstantResolver( 316 QueryEvaluator queryEvaluator, PrimaryExpression primaryExpression, 317 FieldMetaRole role, Object constant, boolean negate 318 ) 319 { 320 super(queryEvaluator, primaryExpression, role, negate); 321 this.constant = constant; 322 } 323 324 private static Set<Long> emptyDataEntryIDs = Collections.emptySet(); 325 326 @Override 327 public Set<Long> _queryEnd(FieldMeta fieldMeta, ClassMeta classMeta, AbstractMemberMetaData mmd, 328 FieldMeta subFieldMeta, boolean argumentIsPersistent, Class<?> argumentType) 329 { 330 if (constant != null && !argumentType.isInstance(constant)) { 331 logger.debug( 332 "_queryEnd: constant {} is of type {} but field {} is of type {} and thus constant cannot be contained. Returning empty set!", 333 new Object[] { 334 constant, constant.getClass().getName(), fieldMeta, argumentType.getClass().getName() 335 } 336 ); 337 return negateIfNecessary(fieldMeta, emptyDataEntryIDs); 338 } 339 340 if (argumentIsPersistent) { 341 Long constantDataEntryID = null; 342 if (constant != null) { 343 ClassMeta constantClassMeta = queryEvaluator.getStoreManager().getClassMeta(executionContext, constant.getClass()); 344 Object constantID = executionContext.getApiAdapter().getIdForObject(constant); 345 if (constantID == null) 346 throw new IllegalStateException("The ApiAdapter returned null as object-ID for: " + constant); 347 348 if (mmd.getMappedBy() != null) { 349 DataEntry constantDataEntry = new DataEntryDAO( 350 queryEvaluator.getPersistenceManagerForData(), cryptoContext.getKeyStoreRefID() 351 ).getDataEntry( 352 constantClassMeta, constantID.toString() 353 ); 354 ObjectContainer constantObjectContainer = queryEvaluator.getEncryptionHandler().decryptDataEntry(cryptoContext, constantDataEntry); 355 Object value = constantObjectContainer.getValue( 356 fieldMeta.getMappedByFieldMeta(executionContext).getFieldID() 357 ); 358 359 Long mappedByDataEntryID = (Long) value; 360 if (mappedByDataEntryID == null) 361 return negateIfNecessary(fieldMeta, emptyDataEntryIDs); 362 else 363 return negateIfNecessary(fieldMeta, Collections.singleton(mappedByDataEntryID)); 364 } 365 366 constantDataEntryID = new DataEntryDAO( 367 queryEvaluator.getPersistenceManagerForData(), cryptoContext.getKeyStoreRefID() 368 ).getDataEntryID(constantClassMeta, constantID.toString()); 369 } 370 // IndexEntry indexEntry = IndexEntryObjectRelationHelper.getIndexEntry(cryptoContext, 371 // queryEvaluator.getPersistenceManagerForIndex(), subFieldMeta, classMeta, constantDataEntryID); 372 // ClassMeta fieldOrElementTypeClassMeta = subFieldMeta.getFieldOrElementTypeClassMeta(executionContext); 373 List<IndexEntry> indexEntries = IndexEntryObjectRelationHelper.getIndexEntriesIncludingSubClasses( 374 cryptoContext, queryEvaluator.getPersistenceManagerForIndex(), subFieldMeta, fieldMeta.getClassMeta(), constantDataEntryID 375 ); 376 return negateIfNecessary(fieldMeta, getDataEntryIDsFromIndexEntries(indexEntries)); 377 } 378 else if (subFieldMeta.getMappedByFieldMeta(executionContext) != null) { 379 FieldMeta oppositeFieldMeta = subFieldMeta.getMappedByFieldMeta(executionContext); 380 IndexEntryFactory indexEntryFactory = 381 queryEvaluator.getStoreManager().getIndexFactoryRegistry().getIndexEntryFactory(executionContext, oppositeFieldMeta, true); 382 383 if (indexEntryFactory == null) 384 return negateIfNecessary(fieldMeta, emptyDataEntryIDs); 385 386 // IndexEntry indexEntry = indexEntryFactory.getIndexEntry(cryptoContext, queryEvaluator.getPersistenceManagerForIndex(), oppositeFieldMeta, classMeta, constant); 387 // ClassMeta fieldOrElementTypeClassMeta = oppositeFieldMeta.getFieldOrElementTypeClassMeta(executionContext); 388 List<IndexEntry> indexEntries = indexEntryFactory.getIndexEntriesIncludingSubClasses( 389 cryptoContext, queryEvaluator.getPersistenceManagerForIndex(), oppositeFieldMeta, oppositeFieldMeta.getClassMeta(), constant 390 ); 391 if (indexEntries.isEmpty()) 392 return negateIfNecessary(fieldMeta, emptyDataEntryIDs); 393 394 Set<Long> result = null; 395 for (IndexEntry indexEntry : indexEntries) { 396 IndexValue indexValue = queryEvaluator.getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry); 397 if (result == null) 398 result = new HashSet<Long>(indexValue.getDataEntryIDs().size()); 399 400 for (Long elementDataEntryID : indexValue.getDataEntryIDs()) { 401 DataEntry elementDataEntry = new DataEntryDAO( 402 queryEvaluator.getPersistenceManagerForData(), cryptoContext.getKeyStoreRefID() 403 ).getDataEntry(elementDataEntryID); 404 ObjectContainer elementObjectContainer = queryEvaluator.getEncryptionHandler().decryptDataEntry(cryptoContext, elementDataEntry); 405 Object value = elementObjectContainer.getValue( 406 fieldMeta.getMappedByFieldMeta(executionContext).getFieldID() 407 ); 408 409 Long mappedByDataEntryID = (Long) value; 410 if (mappedByDataEntryID != null) 411 result.add(mappedByDataEntryID); 412 } 413 } 414 return negateIfNecessary(fieldMeta, result); 415 } 416 else { 417 IndexEntryFactory indexEntryFactory = queryEvaluator.getStoreManager().getIndexFactoryRegistry().getIndexEntryFactory(executionContext, subFieldMeta, true); 418 if (indexEntryFactory == null) 419 return negateIfNecessary(fieldMeta, emptyDataEntryIDs); 420 421 // IndexEntry indexEntry = indexEntryFactory.getIndexEntry(cryptoContext, queryEvaluator.getPersistenceManagerForIndex(), subFieldMeta, classMeta, constant); 422 // if (indexEntry == null) 423 // return negateIfNecessary(fieldMeta, emptyDataEntryIDs); 424 // 425 // IndexValue indexValue = queryEvaluator.getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry); 426 // return negateIfNecessary(fieldMeta, indexValue.getDataEntryIDs()); 427 // ClassMeta fieldOrElementTypeClassMeta = subFieldMeta.getFieldOrElementTypeClassMeta(executionContext); 428 List<IndexEntry> indexEntries = indexEntryFactory.getIndexEntriesIncludingSubClasses( 429 cryptoContext, queryEvaluator.getPersistenceManagerForIndex(), subFieldMeta, fieldMeta.getClassMeta(), constant 430 ); 431 return negateIfNecessary(fieldMeta, getDataEntryIDsFromIndexEntries(indexEntries)); 432 } 433 } 434 435 protected Set<Long> getDataEntryIDsFromIndexEntries(Collection<? extends IndexEntry> indexEntries) { 436 if (indexEntries.isEmpty()) 437 return emptyDataEntryIDs; 438 439 Set<Long> dataEntryIDs = null; 440 for (IndexEntry indexEntry : indexEntries) { 441 IndexValue indexValue = queryEvaluator.getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry); 442 if (dataEntryIDs == null) 443 dataEntryIDs = indexEntries.size() == 1 ? indexValue.getDataEntryIDs() : new HashSet<Long>(indexValue.getDataEntryIDs()); 444 else 445 dataEntryIDs.addAll(indexValue.getDataEntryIDs()); 446 } 447 return dataEntryIDs; 448 } 449 } 450 451 public static String getOperatorAsJDOQLSymbol(Operator operator, boolean negate) 452 { 453 if (Expression.OP_EQ == operator) 454 return negate ? "!=" : "=="; 455 if (Expression.OP_NOTEQ == operator) 456 return negate ? "==" : "!="; 457 if (Expression.OP_LT == operator) 458 return negate ? ">=" : "<"; 459 if (Expression.OP_LTEQ == operator) 460 return negate ? ">" : "<="; 461 if (Expression.OP_GT == operator) 462 return negate ? "<=" : ">"; 463 if (Expression.OP_GTEQ == operator) 464 return negate ? "<" : ">="; 465 466 throw new UnsupportedOperationException("NYI"); 467 } 468 }