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