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.ArrayList;
021 import java.util.Collections;
022 import java.util.HashMap;
023 import java.util.HashSet;
024 import java.util.LinkedList;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.Set;
028
029 import javax.jdo.identity.LongIdentity;
030
031 import org.cumulus4j.store.model.DataEntry;
032 import org.cumulus4j.store.query.QueryEvaluator;
033 import org.datanucleus.metadata.AbstractClassMetaData;
034 import org.datanucleus.metadata.AbstractMemberMetaData;
035 import org.datanucleus.query.expression.Expression;
036 import org.datanucleus.query.expression.PrimaryExpression;
037 import org.datanucleus.query.expression.VariableExpression;
038 import org.datanucleus.query.symbol.Symbol;
039
040 /**
041 * <p>
042 * Abstract base class for all {@link Expression} evaluators.
043 * </p>
044 * <p>
045 * DataNucleus gives the query implementation a tree composed of {@link Expression}s. This tree is nothing more
046 * than an object-oriented representation of the query to be executed. In order to actually query data, there
047 * needs to be evaluation logic applying the <code>Expression</code> to the Cumulus4j data structure. This logic is
048 * implemented in subclasses of <code>AbstractExpressionEvaluator</code>.
049 * </p>
050 *
051 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
052 *
053 * @param <X> the {@link Expression} to be evaluated.
054 */
055 public abstract class AbstractExpressionEvaluator<X extends Expression>
056 {
057 private static final boolean CHECK_RESULT_DATA_ENTRY_IDS = false;
058
059 private QueryEvaluator queryEvaluator;
060
061 private AbstractExpressionEvaluator<?> parent;
062
063 private X expression;
064
065 private Map<ResultDescriptor, Set<Long>> resultDescriptor2resultDataEntryIDs = new HashMap<ResultDescriptor, Set<Long>>();
066
067 private Map<ResultDescriptor, List<Object>> resultDescriptor2resultObjects = new HashMap<ResultDescriptor, List<Object>>();
068
069 /**
070 * Create an <code>AbstractExpressionEvaluator</code> instance.
071 * @param queryEvaluator the evaluator responsible for evaluating the entire query. Must not be <code>null</code>.
072 * @param parent the parent-node in the tree. The <code>AbstractExpressionEvaluator</code>s form a tree analogue to the
073 * tree provided by DataNucleus. This <code>parent</code> is <code>null</code>, if it is the root of the tree.
074 * @param expression the expression that is to be evaluated by this <code>AbstractExpressionEvaluator</code> instance.
075 */
076 public AbstractExpressionEvaluator(QueryEvaluator queryEvaluator, AbstractExpressionEvaluator<?> parent, X expression)
077 {
078 if (queryEvaluator == null)
079 throw new IllegalArgumentException("queryEvaluator == null");
080
081 if (expression == null)
082 throw new IllegalArgumentException("expression == null");
083
084 this.queryEvaluator = queryEvaluator;
085
086 this.parent = parent;
087 this.expression = expression;
088 }
089
090 /**
091 * Get the evaluator responsible for evaluating the entire query.
092 * @return the evaluator responsible for evaluating the entire query.
093 */
094 public QueryEvaluator getQueryEvaluator() {
095 return queryEvaluator;
096 }
097
098 /**
099 * Get the parent-node in the tree. The <code>AbstractExpressionEvaluator</code>s form a tree analogue to the
100 * tree provided by DataNucleus. This <code>parent</code> is <code>null</code>, if it is the root of the tree.
101 * @return the parent-node in the tree or <code>null</code>, if this is the root.
102 */
103 public AbstractExpressionEvaluator<?> getParent() {
104 return parent;
105 }
106
107 /**
108 * Get the expression that is to be evaluated by this <code>AbstractExpressionEvaluator</code>.
109 * @return the expression that is to be evaluated by this <code>AbstractExpressionEvaluator</code>.
110 */
111 public X getExpression() {
112 return expression;
113 }
114
115 private AbstractExpressionEvaluator<? extends Expression> left;
116
117 private AbstractExpressionEvaluator<? extends Expression> right;
118
119 /**
120 * Get the left branch in the tree structure.
121 * @return the left branch in the tree structure or <code>null</code> if there is none.
122 * @see #setLeft(AbstractExpressionEvaluator)
123 * @see #getRight()
124 */
125 public AbstractExpressionEvaluator<? extends Expression> getLeft() {
126 return left;
127 }
128 /**
129 * Set the left branch in the tree structure.
130 * @param left the left branch in the tree structure or <code>null</code> if there is none.
131 * @see #getLeft()
132 */
133 public void setLeft(AbstractExpressionEvaluator<? extends Expression> left)
134 {
135 if (left != null && !this.equals(left.getParent()))
136 throw new IllegalArgumentException("this != left.parent");
137
138 this.left = left;
139 }
140
141 /**
142 * Get the right branch in the tree structure.
143 * @return the right branch in the tree structure or <code>null</code> if there is none.
144 * @see #setRight(AbstractExpressionEvaluator)
145 * @see #getLeft()
146 */
147 public AbstractExpressionEvaluator<? extends Expression> getRight() {
148 return right;
149 }
150 /**
151 * Set the right branch in the tree structure.
152 * @param right the right branch in the tree structure or <code>null</code> if there is none.
153 * @see #getRight()
154 */
155 public void setRight(AbstractExpressionEvaluator<? extends Expression> right)
156 {
157 if (right != null && !this.equals(right.getParent()))
158 throw new IllegalArgumentException("this != right.parent");
159
160 this.right = right;
161 }
162
163 private Set<Symbol> resultSymbols;
164
165 /**
166 * <p>
167 * Get the {@link Symbol}s for which {@link #queryResultDataEntryIDs(ResultDescriptor)} (and thus
168 * {@link #queryResultObjects(ResultDescriptor)}) can return a result. For all other {@link Symbol}s,
169 * said methods return <code>null</code>.
170 * </p>
171 * <p>
172 * The implementation in {@link AbstractExpressionEvaluator} delegates to
173 * {@link #_getResultSymbols()} and caches the result.
174 * </p>
175 * <p>
176 * Do <b>not</b> override this method, if you're not absolutely sure you want to
177 * deactivate/override the caching! In most cases, you should override
178 * {@link #_getResultSymbols()} instead.
179 * </p>
180 *
181 * @return the queryable {@link Symbol}s; never <code>null</code>.
182 * @see #_getResultSymbols()
183 */
184 public final Set<Symbol> getResultSymbols()
185 {
186 if (resultSymbols == null) {
187 Set<Symbol> s = _getResultSymbols();
188 if (s == null)
189 s = Collections.emptySet();
190
191 resultSymbols = s;
192 }
193
194 return resultSymbols;
195 }
196
197 /**
198 * <p>
199 * Get the {@link Symbol}s for which {@link #queryResultDataEntryIDs(ResultDescriptor)} (and thus
200 * {@link #queryResultObjects(ResultDescriptor)}) can return a result. For all other {@link Symbol}s,
201 * said methods return <code>null</code>.
202 * </p>
203 * <p>
204 * The default implementation in {@link AbstractExpressionEvaluator} collects the result-symbols
205 * from its {@link #getLeft() left} and its {@link #getRight() right} side and returns this combined
206 * <code>Set</code>.
207 * </p>
208 * <p>
209 * This is the actual implementation of {@link #getResultSymbols()} and should be overridden
210 * instead of the non-"_"-prefixed version, in most cases.
211 * </p>
212 *
213 * @return the queryable {@link Symbol}s or <code>null</code> (<code>null</code> is equivalent to an
214 * empty <code>Set</code>).
215 * @see #getResultSymbols()
216 */
217 protected Set<Symbol> _getResultSymbols()
218 {
219 Set<Symbol> resultSymbols;
220 if (left != null && right == null)
221 resultSymbols = left.getResultSymbols();
222 else if (left == null && right != null)
223 resultSymbols = right.getResultSymbols();
224 else if (left == null && right == null)
225 resultSymbols = Collections.emptySet();
226 else {
227 Set<Symbol> result = new HashSet<Symbol>(left.getResultSymbols().size() + right.getResultSymbols().size());
228 result.addAll(left.getResultSymbols());
229 result.addAll(right.getResultSymbols());
230 resultSymbols = Collections.unmodifiableSet(result);
231 }
232 return resultSymbols;
233 }
234
235 /**
236 * <p>
237 * Get those {@link DataEntry#getDataEntryID() dataEntryID}s that match the query
238 * criteria for the specified <code>resultDescriptor</code> or <code>null</code>,
239 * if the given {@link ResultDescriptor#getSymbol() symbol} is not queryable by the
240 * evaluator implementation.
241 * </p>
242 * <p>
243 * This method delegates to {@link #_queryResultDataEntryIDs(ResultDescriptor)} and caches the
244 * result. Thus a second call to this method with the same symbol does not trigger a
245 * second query but instead immediately returns the cached result.
246 * </p>
247 * <p>
248 * If the subclass of {@link AbstractExpressionEvaluator} does not support querying on its
249 * own (e.g. querying a {@link LiteralEvaluator literal} makes no sense at all), this method
250 * throws an {@link UnsupportedOperationException}. The same exception is thrown, if the requested
251 * query functionality is not yet implemented.
252 * </p>
253 *
254 * @param resultDescriptor the descriptor specifying what candidates (usually "this" or a variable) the
255 * caller is interested in as well as modifiers (e.g. {@link ResultDescriptor#isNegated() negation})
256 * affecting the query.
257 * @return those {@link DataEntry#getDataEntryID() dataEntryID}s that match the query
258 * criteria for the specified <code>resultSymbol</code> or <code>null</code>, if the symbol is not
259 * supported (this should be consistent with the implementation of {@link #_getResultSymbols()}).
260 * @throws UnsupportedOperationException if the implementation does not support querying at all
261 * (e.g. because it makes no sense without more context) or if the concrete query situation is not
262 * yet supported.
263 * @see #_queryResultDataEntryIDs(ResultDescriptor)
264 */
265 public final Set<Long> queryResultDataEntryIDs(ResultDescriptor resultDescriptor)
266 throws UnsupportedOperationException
267 {
268 getQueryEvaluator().pushResultDescriptor(resultDescriptor);
269 try {
270 Set<Long> resultDataEntryIDs = resultDescriptor2resultDataEntryIDs.get(resultDescriptor);
271 if (!resultDescriptor2resultDataEntryIDs.containsKey(resultDescriptor)) {
272 resultDataEntryIDs = _queryResultDataEntryIDs(resultDescriptor);
273
274 if (CHECK_RESULT_DATA_ENTRY_IDS)
275 checkResultDataEntryIDs(resultDataEntryIDs);
276
277 if (resultDataEntryIDs != null)
278 resultDataEntryIDs = Collections.unmodifiableSet(resultDataEntryIDs);
279
280 resultDescriptor2resultDataEntryIDs.put(resultDescriptor, resultDataEntryIDs);
281 }
282
283 return resultDataEntryIDs;
284 } finally {
285 ResultDescriptor popResultDescriptor = getQueryEvaluator().popResultDescriptor();
286 if (resultDescriptor != popResultDescriptor)
287 throw new IllegalStateException("resultDescriptor != popResultDescriptor");
288 }
289 }
290
291 private void checkResultDataEntryIDs(Set<?> dataEntryIDs) {
292 if (dataEntryIDs == null)
293 return;
294
295 for (Object object : dataEntryIDs) {
296 if (!(object instanceof Long))
297 throw new IllegalStateException(this.getClass().getName() + ": dataEntryIDs contains object which is not a Long: " + object);
298 }
299 }
300
301 /**
302 * Execute a query for the given <code>resultDescriptor</code>. This method should contain
303 * the concrete logic for {@link #queryResultDataEntryIDs(ResultDescriptor)} and must be implemented
304 * by subclasses.
305 *
306 * @param resultDescriptor the descriptor specifying what candidates (usually "this" or a variable) the
307 * caller is interested in as well as modifiers (e.g. {@link ResultDescriptor#isNegated() negation})
308 * affecting the query.
309 * @return those {@link DataEntry#getDataEntryID() dataEntryID}s that match the query
310 * criteria for the specified <code>resultSymbol</code> or <code>null</code>, if the symbol is not
311 * supported (this should be consistent with the implementation of {@link #_getResultSymbols()}).
312 * @throws UnsupportedOperationException if the implementation does not support querying at all
313 * (e.g. because it makes no sense without more context) or if the concrete query situation is not
314 * yet supported.
315 * @see #queryResultDataEntryIDs(ResultDescriptor)
316 */
317 protected abstract Set<Long> _queryResultDataEntryIDs(ResultDescriptor resultDescriptor)
318 throws UnsupportedOperationException;
319
320 /**
321 * <p>
322 * Get those objects that match the query criteria for the specified <code>resultDescriptor</code>
323 * or <code>null</code>, if the given {@link ResultDescriptor#getSymbol() symbol} is not queryable by the
324 * evaluator implementation.
325 * </p>
326 * <p>
327 * This method delegates to {@link #_queryResultObjects(ResultDescriptor)} and caches the
328 * result. Thus a second call to this method with the same symbol does not trigger a
329 * second query but instead immediately returns the cached result.
330 * </p>
331 * <p>
332 * If the subclass of {@link AbstractExpressionEvaluator} does not support querying on its
333 * own (e.g. querying a {@link LiteralEvaluator literal} makes no sense at all), this method
334 * throws an {@link UnsupportedOperationException}. The same exception is thrown, if the requested
335 * query functionality is not yet implemented.
336 * </p>
337 *
338 * @param resultDescriptor the descriptor specifying what candidates (usually "this" or a variable) the
339 * caller is interested in as well as modifiers (e.g. {@link ResultDescriptor#isNegated() negation})
340 * affecting the query.
341 * @return the objects matching the criteria or <code>null</code>, if the given <code>resultSymbol</code>
342 * is not supported (this should be consistent with the implementation of {@link #_getResultSymbols()}).
343 * @throws UnsupportedOperationException if the implementation does not support querying at all
344 * (e.g. because it makes no sense without more context) or if the concrete query situation is not
345 * yet supported.
346 * @see #_queryResultObjects(ResultDescriptor)
347 */
348 public final List<Object> queryResultObjects(ResultDescriptor resultDescriptor)
349 throws UnsupportedOperationException
350 {
351
352 List<Object> resultObjects = resultDescriptor2resultObjects.get(resultDescriptor);
353 if (!resultDescriptor2resultObjects.containsKey(resultDescriptor)) {
354 resultObjects = _queryResultObjects(resultDescriptor);
355
356 if (resultObjects != null)
357 resultObjects = Collections.unmodifiableList(resultObjects);
358
359 resultDescriptor2resultObjects.put(resultDescriptor, resultObjects);
360 }
361
362 return resultObjects;
363 }
364
365 /**
366 * <p>
367 * Get those objects that match the query criteria for the specified <code>resultDescriptor</code>
368 * or <code>null</code>, if the given {@link ResultDescriptor#getSymbol() symbol} is not queryable by the
369 * evaluator implementation.
370 * </p>
371 * <p>
372 * The default implementation of this method in {@link AbstractExpressionEvaluator} calls
373 * {@link #queryResultDataEntryIDs(ResultDescriptor)} and then resolves the corresponding objects
374 * (including decrypting their data).
375 * </p>
376 *
377 * @param resultDescriptor the descriptor specifying what candidates (usually "this" or a variable) the
378 * caller is interested in as well as modifiers (e.g. {@link ResultDescriptor#isNegated() negation})
379 * affecting the query.
380 * @return the objects matching the criteria or <code>null</code>, if the given <code>resultDescriptor</code>
381 * is not supported ({@link ResultDescriptor#getSymbol()} should be consistent with the implementation of
382 * {@link #_getResultSymbols()}).
383 * @throws UnsupportedOperationException
384 * @see #queryResultObjects(ResultDescriptor)
385 */
386 protected List<Object> _queryResultObjects(ResultDescriptor resultDescriptor)
387 throws UnsupportedOperationException
388 {
389 Set<Long> dataEntryIDs = queryResultDataEntryIDs(resultDescriptor);
390 if (dataEntryIDs == null)
391 return null;
392
393 List<Object> resultList = new ArrayList<Object>(dataEntryIDs.size());
394
395 for (Long dataEntryID : dataEntryIDs) {
396 LongIdentity id = new LongIdentity(DataEntry.class, dataEntryID);
397 DataEntry dataEntry = (DataEntry) getQueryEvaluator().getPersistenceManagerForData().getObjectById(id);
398 Object entity = queryEvaluator.getObjectForDataEntry(dataEntry);
399 resultList.add(entity);
400 }
401
402 return resultList;
403 }
404
405 private Class<?> getFieldType(Class<?> clazz, List<String> tuples)
406 {
407 if (clazz == null)
408 throw new IllegalArgumentException("clazz == null");
409
410 tuples = new LinkedList<String>(tuples);
411 String nextTuple = tuples.remove(0);
412 AbstractClassMetaData clazzMetaData = getQueryEvaluator().getStoreManager().getMetaDataManager().getMetaDataForClass(clazz, getQueryEvaluator().getClassLoaderResolver());
413 if (clazzMetaData == null)
414 throw new IllegalStateException("No meta-data found for class " + clazz.getName());
415
416 AbstractMemberMetaData metaDataForMember = clazzMetaData.getMetaDataForMember(nextTuple);
417 if (metaDataForMember == null)
418 throw new IllegalStateException("No meta-data found for field \"" + nextTuple + "\" of class \"" + clazz.getName() + "\"!");
419
420 if (tuples.isEmpty())
421 return metaDataForMember.getType();
422 else
423 return getFieldType(metaDataForMember.getType(), tuples);
424 }
425
426 /**
427 * <p>
428 * Get the field type of the <code>PrimaryExpression</code>. This is always the type of the last element in the list of tuples.
429 * </p>
430 * <p>
431 * For example, if the given <code>PrimaryExpression</code> references
432 * <code>this.rating.name</code> and the field <code>name</code> of the class <code>Rating</code> is a {@link String},
433 * this method returns <code>java.lang.String</code>.
434 * </p>
435 *
436 * @param primaryExpression the <code>PrimaryExpression</code> of which to find out the field's type.
437 * @return the type of the field referenced by the given <code>primaryExpression</code>.
438 */
439 protected Class<?> getFieldType(PrimaryExpression primaryExpression)
440 {
441 if (primaryExpression.getSymbol() != null)
442 return getQueryEvaluator().getValueType(primaryExpression.getSymbol());
443
444 if (primaryExpression.getLeft() instanceof VariableExpression) {
445 Symbol classSymbol = ((VariableExpression)primaryExpression.getLeft()).getSymbol();
446 if (classSymbol == null)
447 throw new IllegalStateException("((VariableExpression)primaryExpression.getLeft()).getSymbol() returned null!");
448
449 return getFieldType(getQueryEvaluator().getValueType(classSymbol), primaryExpression.getTuples());
450 }
451 else
452 throw new UnsupportedOperationException("NYI");
453 }
454 }