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    }