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 }