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;
019    
020    import java.util.Collection;
021    import java.util.Deque;
022    import java.util.HashMap;
023    import java.util.HashSet;
024    import java.util.Iterator;
025    import java.util.LinkedList;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Set;
029    
030    import javax.jdo.PersistenceManager;
031    
032    import org.cumulus4j.store.Cumulus4jStoreManager;
033    import org.cumulus4j.store.EncryptionHandler;
034    import org.cumulus4j.store.PersistenceManagerConnection;
035    import org.cumulus4j.store.crypto.CryptoContext;
036    import org.cumulus4j.store.model.ClassMeta;
037    import org.cumulus4j.store.model.ClassMetaDAO;
038    import org.cumulus4j.store.model.DataEntry;
039    import org.cumulus4j.store.model.EmbeddedClassMeta;
040    import org.cumulus4j.store.query.eval.AbstractExpressionEvaluator;
041    import org.cumulus4j.store.query.eval.AndExpressionEvaluator;
042    import org.cumulus4j.store.query.eval.ComparisonExpressionEvaluator;
043    import org.cumulus4j.store.query.eval.InvokeExpressionEvaluator;
044    import org.cumulus4j.store.query.eval.LiteralEvaluator;
045    import org.cumulus4j.store.query.eval.NotExpressionEvaluator;
046    import org.cumulus4j.store.query.eval.OrExpressionEvaluator;
047    import org.cumulus4j.store.query.eval.ParameterExpressionEvaluator;
048    import org.cumulus4j.store.query.eval.PrimaryExpressionEvaluator;
049    import org.cumulus4j.store.query.eval.ResultDescriptor;
050    import org.cumulus4j.store.query.eval.SubqueryExpressionEvaluator;
051    import org.cumulus4j.store.query.eval.VariableExpressionEvaluator;
052    import org.datanucleus.ClassLoaderResolver;
053    import org.datanucleus.ExecutionContext;
054    import org.datanucleus.identity.IdentityUtils;
055    import org.datanucleus.metadata.AbstractClassMetaData;
056    import org.datanucleus.query.QueryUtils;
057    import org.datanucleus.query.compiler.QueryCompilation;
058    import org.datanucleus.query.expression.DyadicExpression;
059    import org.datanucleus.query.expression.Expression;
060    import org.datanucleus.query.expression.InvokeExpression;
061    import org.datanucleus.query.expression.Literal;
062    import org.datanucleus.query.expression.ParameterExpression;
063    import org.datanucleus.query.expression.PrimaryExpression;
064    import org.datanucleus.query.expression.SubqueryExpression;
065    import org.datanucleus.query.expression.VariableExpression;
066    import org.datanucleus.query.symbol.Symbol;
067    import org.datanucleus.store.query.Query;
068    
069    /**
070     * API-agnostic query implementation. An instance of this class performs the actual query.
071     * It is used by both APIs, JDO and JPA.
072     *
073     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
074     */
075    public abstract class QueryEvaluator
076    {
077            /** Name under which any set of results are stored in the state map. Used for aggregation. */
078            public static final String RESULTS_SET = "DATANUCLEUS_RESULTS_SET";
079    
080            private final String language;
081    
082            private String candidateAlias = "this";
083    
084            /** Underlying "string-based" query. */
085            private Query query;
086    
087            /** Compilation of the underlying query, that we are evaluating. */
088            private QueryCompilation compilation;
089    
090            /** Map of input parameter values, keyed by the parameter name. */
091            private Map<String, Object> parameterValues;
092    
093            /** Positional parameter that we are up to (-1 implies not being used). */
094            private Map<Integer, Symbol> paramSymbolByPosition = null;
095    
096            /** Map of state symbols for the query evaluation. */
097            private Map<String, Object> state;
098    
099            private ClassLoaderResolver clr;
100    
101            private ExecutionContext ec;
102    
103            private Cumulus4jStoreManager storeManager;
104    
105            private CryptoContext cryptoContext;
106    
107            private PersistenceManagerConnection pmConn;
108    
109            private EncryptionHandler encryptionHandler;
110    
111            private boolean complete = true;
112    
113            private Map<Symbol, EmbeddedClassMeta> symbol2ValueTypeEmbeddedClassMeta = null;
114    
115            /**
116             * @param language Query language (JDOQL, JPQL, etc)
117             * @param compilation generic compilation
118             * @param parameterValues Input values for the params
119             * @param clr ClassLoader resolver
120             * @param pmConn our <b>backend</b>-<code>PersistenceManager</code> connection(s).
121             * @param cryptoContext TODO
122             */
123            public QueryEvaluator(
124                            String language, Query query, QueryCompilation compilation, Map<String, Object> parameterValues,
125                            ClassLoaderResolver clr, PersistenceManagerConnection pmConn, CryptoContext cryptoContext)
126            {
127                    this.language = language;
128                    this.query = query;
129                    this.compilation = compilation;
130                    this.parameterValues = parameterValues;
131                    this.clr = clr;
132                    this.ec = query.getExecutionContext();
133                    this.storeManager = (Cumulus4jStoreManager) query.getStoreManager();
134                    this.pmConn = pmConn;
135                    this.cryptoContext = cryptoContext;
136                    this.encryptionHandler = storeManager.getEncryptionHandler();
137    
138                    this.candidateAlias = (compilation.getCandidateAlias() != null ? compilation.getCandidateAlias() : this.candidateAlias);
139    
140                    state = new HashMap<String, Object>();
141                    state.put(this.candidateAlias, query.getCandidateClass());
142    
143                    if (parameterValues != null && !parameterValues.isEmpty()) {
144                            Object paramKey = parameterValues.keySet().iterator().next();
145                            if (paramKey instanceof Integer) {
146                                    paramSymbolByPosition = new HashMap<Integer, Symbol>();
147                            }
148                    }
149            }
150    
151            public boolean isComplete() {
152                    return complete;
153            }
154    
155            public void setIncomplete() {
156                    this.complete = false;
157            }
158    
159            public String getLanguage() {
160                    return language;
161            }
162    
163            public String getCandidateAlias() {
164                    return candidateAlias;
165            }
166    
167            public Query getQuery() {
168                    return query;
169            }
170    
171            public QueryCompilation getCompilation() {
172                    return compilation;
173            }
174    
175            public Map<String, Object> getParameterValues() {
176                    return parameterValues;
177            }
178    
179            public Map<String, Object> getState() {
180                    return state;
181            }
182    
183            public ClassLoaderResolver getClassLoaderResolver() {
184                    return clr;
185            }
186    
187            public ExecutionContext getExecutionContext() {
188                    return ec;
189            }
190    
191            public Cumulus4jStoreManager getStoreManager() {
192                    return storeManager;
193            }
194    
195            public PersistenceManagerConnection getPersistenceManagerConnection() {
196                    return pmConn;
197            }
198    
199            public PersistenceManager getPersistenceManagerForData() {
200                    return pmConn.getDataPM();
201            }
202    
203            public PersistenceManager getPersistenceManagerForIndex() {
204                    return pmConn.getIndexPM();
205            }
206    
207            public EncryptionHandler getEncryptionHandler() {
208                    return encryptionHandler;
209            }
210    
211            private Deque<ResultDescriptor> resultDescriptors = new LinkedList<ResultDescriptor>();
212    
213            /**
214             * Push a {@link ResultDescriptor} onto the stack.
215             * @param resultDescriptor the descriptor to be pushed.
216             */
217            public void pushResultDescriptor(ResultDescriptor resultDescriptor)
218            {
219                    resultDescriptors.push(resultDescriptor);
220            }
221    
222            /**
223             * Pop a {@link ResultDescriptor} from the stack.
224             * @return the popped descriptor (which is the last one pushed).
225             */
226            public ResultDescriptor popResultDescriptor()
227            {
228                    return resultDescriptors.pop();
229            }
230    
231            public ClassMeta getValueTypeClassMeta(Symbol symbol, boolean throwExceptionIfNotFound) {
232                    ClassMeta classMeta = getValueTypeEmbeddedClassMeta(symbol);
233                    if (classMeta == null) {
234                            Class<?> clazz = getValueType(symbol, throwExceptionIfNotFound);
235                            classMeta = getStoreManager().getClassMeta(getExecutionContext(), clazz);
236                    }
237                    return classMeta;
238            }
239    
240            public EmbeddedClassMeta getValueTypeEmbeddedClassMeta(Symbol symbol) {
241                    if (symbol == null)
242                            throw new IllegalArgumentException("symbol == null");
243    
244                    if (symbol2ValueTypeEmbeddedClassMeta == null)
245                            return null;
246    
247                    return symbol2ValueTypeEmbeddedClassMeta.get(symbol);
248            }
249    
250            public void registerValueTypeEmbeddedClassMeta(Symbol symbol, EmbeddedClassMeta embeddedClassMeta) {
251                    if (symbol == null)
252                            throw new IllegalArgumentException("symbol == null");
253    
254                    if (embeddedClassMeta == null)
255                            return;
256    
257                    if (symbol2ValueTypeEmbeddedClassMeta == null)
258                            symbol2ValueTypeEmbeddedClassMeta = new HashMap<Symbol, EmbeddedClassMeta>();
259    
260                    symbol2ValueTypeEmbeddedClassMeta.put(symbol, embeddedClassMeta);
261            }
262    
263            /**
264             * Get a <code>Symbol</code>'s {@link Symbol#getValueType() valueType} by taking {@link ResultDescriptor}s into account.
265             * Delegates to {@link #getValueType(Symbol, boolean)} with <code>throwExceptionIfNotFound == true</code>.
266             *
267             * @param symbol the symbol whose {@link Symbol#getValueType() valueType} should be resolved.
268             * @return the type - never <code>null</code>.
269             * @see #getValueType(Symbol, boolean)
270             */
271            public Class<?> getValueType(Symbol symbol)
272            {
273                    return getValueType(symbol, true);
274            }
275    
276            /**
277             * <p>
278             * Get a <code>Symbol</code>'s {@link Symbol#getValueType() valueType} by taking {@link ResultDescriptor}s into account.
279             * </p>
280             * <p>
281             * This method (or alternatively {@link #getValueType(Symbol)}) should always be used instead of directly
282             * accessing {@link Symbol#getValueType()}!!! This allows for implicit variables (which are not declared).
283             * </p>
284             * <p>
285             * This method first checks, if {@link Symbol#getValueType()} returns a value and if so, returns it. Otherwise
286             * it searches the stack of {@link ResultDescriptor}s (maintained via {@link #pushResultDescriptor(ResultDescriptor)}
287             * and {@link #popResultDescriptor()}) and returns the first found {@link ResultDescriptor#getResultType()}.
288             * </p>
289             *
290             * @param symbol the symbol whose {@link Symbol#getValueType() valueType} should be resolved.
291             * @param throwExceptionIfNotFound whether to throw an {@link IllegalStateException} [exception type might be changed without notice!],
292             * if the type cannot be resolved. If <code>false</code> this method returns <code>null</code> instead.
293             * @return the type or <code>null</code>, if not resolvable and <code>throwExceptionIfNotFound == false</code>.
294             * @see #getValueType(Symbol)
295             * @see #pushResultDescriptor(ResultDescriptor)
296             * @see #popResultDescriptor()
297             */
298            public Class<?> getValueType(Symbol symbol, boolean throwExceptionIfNotFound)
299            {
300                    if (symbol.getValueType() != null)
301                            return symbol.getValueType();
302    
303                    for (ResultDescriptor resultDescriptor : resultDescriptors) {
304                            if (symbol.equals(resultDescriptor.getSymbol()))
305                                    return resultDescriptor.getResultType();
306                    }
307    
308                    if (symbol.getType() == Symbol.PARAMETER) {
309                            // Cater for implicit parameters where the generic compilation doesn't have the type
310                            if (paramSymbolByPosition != null) {
311                                    // Positional parameters
312                                    Iterator<Map.Entry<Integer, Symbol>> paramIter = paramSymbolByPosition.entrySet().iterator();
313                                    while (paramIter.hasNext()) {
314                                            Map.Entry<Integer, Symbol> entry = paramIter.next();
315                                            if (entry.getValue() == symbol) {
316                                                    return parameterValues.get(entry.getKey()).getClass();
317                                            }
318                                    }
319    
320                                    Integer nextPos = paramSymbolByPosition.size();
321                                    Object value = parameterValues.get(nextPos);
322                                    paramSymbolByPosition.put(nextPos, symbol);
323                                    return value.getClass();
324                            }
325                            else {
326                                    if (parameterValues.containsKey(symbol.getQualifiedName())) {
327                                            return parameterValues.get(symbol.getQualifiedName()).getClass();
328                                    }
329                            }
330                    }
331                    if (throwExceptionIfNotFound)
332                            throw new IllegalStateException("Could not determine the resultType of symbol \"" + symbol + "\"! If this is a variable, you might want to declare it.");
333    
334                    return null;
335            }
336    
337            protected abstract Collection<Object> evaluateSubquery(
338                            Query subquery, QueryCompilation compilation, Object outerCandidate
339            );
340    
341            public List<Object> execute()
342            {
343                    Class<?> candidateClass = query.getCandidateClass();
344                    boolean withSubclasses = query.isSubclasses();
345                    Set<ClassMeta> candidateClassMetas = QueryHelper.getCandidateClassMetas(storeManager, ec, candidateClass, withSubclasses);
346    
347                    // TODO I copied this from the JavaQueryEvaluator, but I'm not sure, whether we need this. Need to talk with Andy. Marco.
348                    // ...or analyse it ourselves (step through)...
349                    String[] subqueryAliases = compilation.getSubqueryAliases();
350                    if (subqueryAliases != null) {
351                            for (int i=0; i<subqueryAliases.length; ++i) {
352                                    // Evaluate subquery first
353                                    Query subquery = query.getSubqueryForVariable(subqueryAliases[i]).getQuery();
354                                    QueryCompilation subqueryCompilation = compilation.getCompilationForSubquery(subqueryAliases[i]);
355    
356                                    Collection<Object> subqueryResult = evaluateSubquery(subquery, subqueryCompilation, null);
357    
358                                    if (QueryUtils.queryReturnsSingleRow(subquery)) {
359                                            Iterator<Object> subqueryIterator = subqueryResult.iterator();
360                                            if (!subqueryIterator.hasNext()) // TODO simply use null in this case?!????
361                                                    throw new IllegalStateException("Subquery is expected to return a single row, but it returned an empty collection!");
362    
363                                            state.put(subqueryAliases[i], subqueryIterator.next());
364    
365                                            if (subqueryIterator.hasNext())
366                                                    throw new IllegalStateException("Subquery is expected to return only a single row, but it returned more than one!");
367                                    }
368                                    else
369                                            state.put(subqueryAliases[i], subqueryResult);
370                            }
371                    }
372    
373                    if (compilation.getExprFilter() == null) {
374                            // No filter - we want all that match the candidate classes.
375                            return QueryHelper.getAllPersistentObjectsForCandidateClasses(cryptoContext, getPersistenceManagerForData(), candidateClassMetas);
376                    }
377                    else {
378                            expressionEvaluator = createExpressionEvaluatorTree(compilation.getExprFilter());
379                            Symbol resultSymbol = getCompilation().getSymbolTable().getSymbol(getCandidateAlias());
380                            if (resultSymbol == null)
381                                    throw new IllegalStateException("getCompilation().getSymbolTable().getSymbol(getCandidateAlias()) returned null! getCandidateAlias()==\"" + getCandidateAlias() + "\"");
382    
383                            return expressionEvaluator.queryResultObjects(new ResultDescriptor(resultSymbol, null));
384                    }
385            }
386    
387            private AbstractExpressionEvaluator<?> expressionEvaluator;
388    
389            public AbstractExpressionEvaluator<?> getExpressionEvaluator() {
390                    return expressionEvaluator;
391            }
392    
393            private AbstractExpressionEvaluator<?> createExpressionEvaluatorTree(Expression expression)
394            {
395                    return createExpressionEvaluatorTreeRecursive(null, expression);
396            }
397    
398            private AbstractExpressionEvaluator<?> createExpressionEvaluatorTreeRecursive(AbstractExpressionEvaluator<?> parent, Expression expression)
399            {
400                    AbstractExpressionEvaluator<?> eval = createExpressionEvaluator(parent, expression);
401    
402                    if (expression.getLeft() != null) {
403                            AbstractExpressionEvaluator<?> childEval = createExpressionEvaluatorTreeRecursive(eval, expression.getLeft());
404                            eval.setLeft(childEval);
405                    }
406    
407                    if (expression.getRight() != null) {
408                            AbstractExpressionEvaluator<?> childEval = createExpressionEvaluatorTreeRecursive(eval, expression.getRight());
409                            eval.setRight(childEval);
410                    }
411    
412                    return eval;
413            }
414    
415            private AbstractExpressionEvaluator<?> createExpressionEvaluator(
416                            AbstractExpressionEvaluator<?> parent,
417                            Expression expr
418            )
419            {
420                    if (expr instanceof DyadicExpression) {
421                            DyadicExpression expression = (DyadicExpression) expr;
422                            if (
423                                            Expression.OP_EQ == expression.getOperator() ||
424                                            Expression.OP_NOTEQ == expression.getOperator() ||
425                                            Expression.OP_LT == expression.getOperator() ||
426                                            Expression.OP_LTEQ == expression.getOperator() ||
427                                            Expression.OP_GT == expression.getOperator() ||
428                                            Expression.OP_GTEQ == expression.getOperator()
429                            )
430                                    return new ComparisonExpressionEvaluator(this, parent, expression);
431                            else if (Expression.OP_AND == expression.getOperator())
432                                    return new AndExpressionEvaluator(this, parent, expression);
433                            else if (Expression.OP_OR == expression.getOperator())
434                                    return new OrExpressionEvaluator(this, parent, expression);
435                            else if (Expression.OP_NOT == expression.getOperator())
436                                    return new NotExpressionEvaluator(this, parent, expression);
437                            else
438                                    throw new UnsupportedOperationException("Unsupported operator for DyadicExpression: " + expr);
439                    }
440    
441                    if (expr instanceof PrimaryExpression)
442                            return new PrimaryExpressionEvaluator(this, parent, (PrimaryExpression) expr);
443    
444                    if (expr instanceof ParameterExpression)
445                            return new ParameterExpressionEvaluator(this, parent, (ParameterExpression) expr);
446    
447                    if (expr instanceof Literal)
448                            return new LiteralEvaluator(this, parent, (Literal) expr);
449    
450                    if (expr instanceof InvokeExpression)
451                            return new InvokeExpressionEvaluator(this, parent, (InvokeExpression) expr);
452    
453                    if (expr instanceof VariableExpression)
454                            return new VariableExpressionEvaluator(this, parent, (VariableExpression) expr);
455    
456                    if (expr instanceof SubqueryExpression)
457                            return new SubqueryExpressionEvaluator(this, parent, (SubqueryExpression) expr);
458    
459                    throw new UnsupportedOperationException("Don't know what to do with this expression: " + expr);
460            }
461    
462            public Object getObjectForDataEntry(DataEntry dataEntry)
463            {
464                    return getObjectForClassMetaAndObjectIDString(dataEntry.getClassMeta(), dataEntry.getObjectID());
465            }
466    
467            public Object getObjectForClassMetaAndObjectIDString(ClassMeta classMeta, String objectIDString)
468            {
469                    AbstractClassMetaData cmd = classMeta.getDataNucleusClassMetaData(ec);
470                    return IdentityUtils.getObjectFromIdString(objectIDString, cmd, ec, true);
471            }
472    
473            public Set<Long> getAllDataEntryIDsForCandidateClasses(Set<ClassMeta> candidateClassMetas)
474            {
475                    javax.jdo.Query q = getPersistenceManagerForData().newQuery(DataEntry.class);
476                    q.setResult("this.dataEntryID");
477    
478    
479                    Map<String, Object> queryParams = new HashMap<String, Object>();
480                    StringBuilder filter = new StringBuilder();
481    
482                    filter.append("this.keyStoreRefID == :keyStoreRefID && ");
483                    queryParams.put("keyStoreRefID", cryptoContext.getKeyStoreRefID());
484    
485                    filter.append(ClassMetaDAO.getMultiClassMetaOrFilterPart(queryParams, candidateClassMetas));
486    
487                    q.setFilter(filter.toString());
488    
489                    @SuppressWarnings("unchecked")
490                    Collection<Long> allDataEntryIDs = (Collection<Long>) q.executeWithMap(queryParams);
491                    Set<Long> result = new HashSet<Long>(allDataEntryIDs);
492                    q.closeAll();
493                    return result;
494            }
495    
496            public CryptoContext getCryptoContext() {
497                    return cryptoContext;
498            }
499    }