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