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 }