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 }