/******************************************************************************* | |
* Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved. | |
* This program and the accompanying materials are made available under the | |
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* Contributors: | |
* Oracle - initial API and implementation from Oracle TopLink | |
******************************************************************************/ | |
package org.eclipse.persistence.expressions; | |
import java.util.*; | |
import java.io.*; | |
import org.eclipse.persistence.descriptors.ClassDescriptor; | |
import org.eclipse.persistence.exceptions.*; | |
import org.eclipse.persistence.history.*; | |
import org.eclipse.persistence.internal.helper.*; | |
import org.eclipse.persistence.internal.sessions.AbstractRecord; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.internal.expressions.*; | |
import org.eclipse.persistence.queries.DatabaseQuery; | |
import org.eclipse.persistence.queries.ReadQuery; | |
import org.eclipse.persistence.sessions.UnitOfWork; | |
/** | |
* <P> | |
* <B>Purpose</B>: Allow for instances of expression to be created. Expressions are Java object-level representations of SQL "where" clauses. | |
* The expressions attempt to mirror Java code as closely as possible.</p> | |
* | |
* <P> | |
* | |
* <B>Example</B>: | |
* <PRE><BLOCKQUOTE> | |
* ExpressionBuilder employee = new ExpressionBuilder(); | |
* employee.get("firstName").equal("Bob").and(employee.get("lastName").equal("Smith")) | |
* | |
* >> equivalent Java code: (employee.getFirstName().equals("Bob")) && (employee.getLastName().equals("Smith")) | |
* | |
* >> equivalent SQL: (F_NAME = 'Bob') AND (L_NAME = 'Smith') | |
* </BLOCKQUOTE></PRE> | |
* | |
* @see Expression | |
*/ | |
public class ExpressionBuilder extends ObjectExpression { | |
protected transient AbstractSession session; | |
protected Class queryClass; | |
protected SQLSelectStatement statement; | |
protected DatabaseTable viewTable; | |
protected DatabaseTable aliasedViewTable; | |
protected boolean wasQueryClassSetInternally = true; | |
protected boolean wasAdditionJoinCriteriaUsed = false; | |
/** | |
* PUBLIC: | |
* Create a new ExpressionBuilder. | |
*/ | |
public ExpressionBuilder() { | |
super(); | |
} | |
/** | |
* ADVANCED: | |
* Create a new ExpressionBuilder representing instances of the argument class. | |
* This can be used for the purpose of parallel expressions. | |
* This is a type of query that searches on the relationship between to un-related objects. | |
*/ | |
public ExpressionBuilder(Class queryClass) { | |
super(); | |
this.queryClass = queryClass; | |
this.wasQueryClassSetInternally = false; | |
} | |
/** | |
* INTERNAL: | |
* Return if the expression is equal to the other. | |
* This is used to allow dynamic expression's SQL to be cached. | |
*/ | |
public boolean equals(Object expression) { | |
if (this == expression) { | |
return true; | |
} | |
// Return false for parallel expressions, as equality is unknown. | |
return super.equals(expression) && ((getQueryClass() == null) && ((ExpressionBuilder)expression).getQueryClass() == null); | |
} | |
/** | |
* INTERNAL: Find the alias for a given table. Handle the special case where we are bogus | |
* and it should be aliased against our derived tables instead. | |
*/ | |
public DatabaseTable aliasForTable(DatabaseTable table) { | |
if (hasViewTable()) { | |
return getAliasedViewTable(); | |
} | |
if (doesNotRepresentAnObjectInTheQuery()) { | |
for (Expression expression : this.derivedTables) { | |
DatabaseTable result = expression.aliasForTable(table); | |
if (result != null) { | |
return result; | |
} | |
} | |
} else { | |
return super.aliasForTable(table); | |
} | |
return null; // No alias found in the derived tables | |
} | |
/** | |
* INTERNAL: | |
* Assign aliases to any tables which I own. Start with t(initialValue), | |
* and return the new value of the counter , i.e. if initialValue is one | |
* and I have tables ADDRESS and EMPLOYEE I will assign them t1 and t2 respectively, and return 3. | |
*/ | |
public int assignTableAliasesStartingAt(int initialValue) { | |
if (hasBeenAliased()) { | |
return initialValue; | |
} | |
if (doesNotRepresentAnObjectInTheQuery()) { | |
return initialValue; | |
} | |
// This block should be removed I think. | |
// The only reason to clone might be to | |
// preserve the qualifier, but aliases need | |
// qualifiers? That seems strange. | |
// Also this will break AsOf queries. By | |
// inference if has view table the AliasTableLookup | |
// will contain one table, and that will be the | |
// table of the view... | |
if (hasViewTable()) { | |
DatabaseTable aliased = viewTable.clone(); | |
String alias = "t" + initialValue; | |
aliased.setName(alias); | |
assignAlias(alias, viewTable); | |
aliasedViewTable = aliased; | |
hasBeenAliased = true; | |
return initialValue + 1; | |
} | |
return super.assignTableAliasesStartingAt(initialValue); | |
} | |
/** | |
* INTERNAL: | |
* Used for debug printing. | |
*/ | |
public String descriptionOfNodeType() { | |
return "Base"; | |
} | |
/** | |
* INTERNAL: | |
* There are cases (which we might want to eliminate?) where the expression builder | |
* doesn't actually correspond to an object to be read. Mostly this is the case where | |
* it's a data query in terms of tables, and the builder is only there to provide a base. | |
* It might be better to make tables able to serve as their own base, but it's very nice | |
* to have a known unique, shared base. In the meantime, this | |
* is a special case to make sure the builder doesn't get tables assigned. | |
*/ | |
public boolean doesNotRepresentAnObjectInTheQuery() { | |
return (hasDerivedTables() && !hasDerivedFields() && !hasDerivedExpressions()); | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public DatabaseTable getAliasedViewTable() { | |
return aliasedViewTable; | |
} | |
/** | |
* INTERNAL: | |
* Return the expression builder which is the ultimate base of this expression, or | |
* null if there isn't one (shouldn't happen if we start from a root) | |
*/ | |
public ExpressionBuilder getBuilder() { | |
return this; | |
} | |
/** | |
* INTERNAL: | |
* Only usable after the session and class have been set. Return the | |
* descriptor for the class this node represents. | |
*/ | |
public ClassDescriptor getDescriptor() { | |
if (descriptor == null) { | |
if (getQueryClass() == null) { | |
return null; | |
} else { | |
if (getSession() == null) { | |
throw QueryException.noExpressionBuilderFound(this); | |
} | |
descriptor = getSession().getDescriptor(getQueryClass()); | |
descriptor = convertToCastDescriptor(descriptor, getSession()); | |
} | |
} | |
return descriptor; | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public Class getQueryClass() { | |
return queryClass; | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public AbstractSession getSession() { | |
return session; | |
} | |
/** | |
* INTERNAL: | |
* Return the statement that expression is for. | |
* This is used for the context in subselects. | |
*/ | |
public SQLSelectStatement getStatement() { | |
return statement; | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public DatabaseTable getViewTable() { | |
return viewTable; | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public boolean hasViewTable() { | |
return viewTable != null; | |
} | |
/** | |
* INTERNAL: | |
*/ | |
public boolean isExpressionBuilder() { | |
return true; | |
} | |
/** | |
* INTERNAL: | |
* Normalize the expression into a printable structure. | |
* Any joins must be added to form a new root. | |
*/ | |
@Override | |
public Expression normalize(ExpressionNormalizer normalizer) { | |
if (hasBeenNormalized()) { | |
return this; | |
} else { | |
setHasBeenNormalized(true); | |
} | |
// Normalize the ON clause if present. Need to use rebuild, not twist as parameters are real parameters. | |
if (this.onClause != null) { | |
this.onClause = this.onClause.normalize(normalizer); | |
if (shouldUseOuterJoin() || (!getSession().getPlatform().shouldPrintInnerJoinInWhereClause())) { | |
normalizer.getStatement().addOuterJoinExpressionsHolders(this, null, null, null); | |
if ((getDescriptor() != null) && (getDescriptor().getHistoryPolicy() != null)) { | |
Expression historyCriteria = getDescriptor().getHistoryPolicy().additionalHistoryExpression(this, this); | |
if (historyCriteria != null) { | |
normalizer.addAdditionalExpression(historyCriteria); | |
} | |
} | |
} else { | |
normalizer.addAdditionalExpression(this.onClause); | |
} | |
} | |
// This is required for parallel selects, | |
// the session must be set and the additional join expression added. | |
if (this.queryClass != null) { | |
Expression criteria = null; | |
setSession(normalizer.getSession().getRootSession(null)); | |
// The descriptor must be defined at this point. | |
if (getDescriptor() == null) { | |
throw QueryException.noExpressionBuilderFound(this); | |
} | |
if (!this.wasAdditionJoinCriteriaUsed) { | |
criteria = getDescriptor().getQueryManager().getAdditionalJoinExpression(); | |
if (criteria != null) { | |
criteria = twist(criteria, this); | |
} | |
} | |
if (isUsingOuterJoinForMultitableInheritance()) { | |
if (getSession().getPlatform().shouldPrintOuterJoinInWhereClause()) { | |
Expression childrenCriteria = getDescriptor().getInheritancePolicy().getChildrenJoinExpression(); | |
childrenCriteria = twist(childrenCriteria, this); | |
childrenCriteria.convertToUseOuterJoin(); | |
if(criteria == null) { | |
criteria = childrenCriteria; | |
} else { | |
criteria = criteria.and(childrenCriteria); | |
} | |
} else { | |
normalizer.getStatement().addOuterJoinExpressionsHolders(null, null, additionalExpressionCriteriaMap(), this.getDescriptor()); | |
// fall through to the main case | |
} | |
} | |
normalizer.addAdditionalExpression(criteria); | |
} | |
setStatement(normalizer.getStatement()); | |
if (getAsOfClause() == null) { | |
asOf(AsOfClause.NO_CLAUSE); | |
} | |
if ((getDescriptor() != null) && (getDescriptor().getHistoryPolicy() != null)) { | |
Expression temporalCriteria = getDescriptor().getHistoryPolicy().additionalHistoryExpression(this, this); | |
normalizer.addAdditionalExpression(temporalCriteria); | |
} | |
ReadQuery query = normalizer.getStatement().getQuery(); | |
// Record any class used in a join to invalidate query results cache. | |
if ((query != null) && query.shouldCacheQueryResults()) { | |
if (this.queryClass != null) { | |
query.getQueryResultsCachePolicy().getInvalidationClasses().add(this.queryClass); | |
} | |
} | |
return this; | |
} | |
/** | |
* INTERNAL: | |
* Print java | |
*/ | |
public void printJava(ExpressionJavaPrinter printer) { | |
printer.printString(printer.getBuilderString()); | |
} | |
/** | |
* INTERNAL: | |
* This expression is built on a different base than the one we want. Rebuild it and | |
* return the root of the new tree | |
* This assumes that the original expression has only a single builder. | |
*/ | |
public Expression rebuildOn(Expression newBase) { | |
return newBase; | |
} | |
/** | |
* INTERNAL: | |
* Search the tree for any expressions (like SubSelectExpressions) that have been | |
* built using a builder that is not attached to the query. This happens in case of an Exists | |
* call using a new ExpressionBuilder(). This builder needs to be replaced with one from the query. | |
*/ | |
public void resetPlaceHolderBuilder(ExpressionBuilder queryBuilder){ | |
return; | |
} | |
/** | |
* INTERNAL: | |
* Override Expression.registerIn to check if the new base expression | |
* has already been provided for the clone. | |
* @see org.eclipse.persistence.expressions.Expression#cloneUsing(Expression) | |
* @bug 2637484 INVALID QUERY KEY EXCEPTION THROWN USING BATCH READS AND PARALLEL EXPRESSIONS | |
*/ | |
protected Expression registerIn(Map alreadyDone) { | |
// Here do a special check to see if this a cloneUsing(newBase) call. | |
Object value = alreadyDone.get(alreadyDone); | |
if ((value == null) || (value == alreadyDone)) { | |
// This is a normal cloning operation. | |
return super.registerIn(alreadyDone); | |
} | |
ObjectExpression copy = (ObjectExpression)value; | |
// copy is actually the newBase of a cloneUsing. | |
alreadyDone.put(alreadyDone, alreadyDone); | |
alreadyDone.put(this, copy); | |
// Now need to copy over the derived expressions, etc. | |
if (this.derivedExpressions != null) { | |
if (copy.derivedExpressions == null) { | |
copy.derivedExpressions = copyDerivedExpressions(alreadyDone); | |
} else { | |
copy.derivedExpressions.addAll(copyDerivedExpressions(alreadyDone)); | |
} | |
} | |
// Do the same for these protected fields. | |
copy.postCopyIn(alreadyDone, this.derivedFields, this.derivedTables); | |
return copy; | |
} | |
/** | |
* INTERNAL: | |
* Set the class which this node represents. | |
*/ | |
public void setQueryClass(Class queryClass) { | |
this.queryClass = queryClass; | |
this.descriptor = null; | |
} | |
/** | |
* INTERNAL: | |
* Set the class and descriptor which this node represents. | |
*/ | |
public void setQueryClassAndDescriptor(Class queryClass, ClassDescriptor descriptor) { | |
this.queryClass = queryClass; | |
this.descriptor = convertToCastDescriptor(descriptor, session); | |
} | |
/** | |
* INTERNAL: | |
* Set the session in which we expect this expression to be translated. | |
* Stored session shall always be root session. | |
*/ | |
public void setSession(AbstractSession session) { | |
this.session = session.getRootSession(null); | |
} | |
/** | |
* INTERNAL: | |
* Set the statement that expression is for. | |
* This is used for the context in subselects. | |
*/ | |
public void setStatement(SQLSelectStatement statement) { | |
this.statement = statement; | |
} | |
/** | |
* INTERNAL: | |
* This expression represents something read through a view table. | |
*/ | |
public void setViewTable(DatabaseTable theTable) { | |
viewTable = theTable; | |
} | |
/** | |
* INTERNAL: | |
* If the additional Join Criteria for the class this builder represents has | |
* been added to the statement then mark this as true. This will prevent | |
* TopLink from adding it again at normalization | |
*/ | |
public void setWasAdditionJoinCriteriaUsed(boolean joinCriteriaUsed){ | |
this.wasAdditionJoinCriteriaUsed = joinCriteriaUsed; | |
} | |
/** | |
* INTERNAL: | |
* Rebuild myself against the base, with the values of parameters supplied by the context | |
* expression. This is used for transforming a standalone expression (e.g. the join criteria of a mapping) | |
* into part of some larger expression. You normally would not call this directly, instead calling twist | |
* See the comment there for more details" | |
* @param newBase | |
* @param context | |
* @return | |
*/ | |
@Override | |
public Expression twistedForBaseAndContext(Expression newBase, Expression context, Expression oldBase) { | |
// TODO: this is wrong, it should only return the newBase if equal to the old base | |
// since twist needs to copy as it twists it needs a map to keep track of new bases | |
return newBase; | |
} | |
/** | |
* INTERNAL: | |
* The expression builder represent the entire object, just return it. | |
*/ | |
public Object valueFromObject(Object object, AbstractSession session, AbstractRecord translationRow, int valueHolderPolicy, boolean isObjectUnregistered) { | |
return object; | |
} | |
/** | |
* INTERNAL: | |
* If the additional Join Criteria for the class this builder represents has | |
* been added to the statement this method will return true; | |
*/ | |
public boolean wasAdditionJoinCriteriaUsed(){ | |
return this.wasAdditionJoinCriteriaUsed; | |
} | |
/** | |
* INTERNAL: | |
* Returns true if TopLink set the query class as opposed to the customer. This | |
* is important in determining if this Expression should be treated as a parallel | |
* expression during normalization | |
*/ | |
public boolean wasQueryClassSetInternally(){ | |
return this.wasQueryClassSetInternally; | |
} | |
/** | |
* INTERNAL: | |
* Lookup the descriptor for this item by traversing its expression recursively. | |
*/ | |
@Override | |
public ClassDescriptor getLeafDescriptor(DatabaseQuery query, ClassDescriptor rootDescriptor, AbstractSession session) { | |
// The base case | |
// The following special case is where there is a parallel builder | |
// which has a different reference class as the primary builder. | |
Class queryClass = getQueryClass(); | |
if ((queryClass != null) && ((query == null) || (queryClass != query.getReferenceClass()))) { | |
return convertToCastDescriptor( session.getDescriptor(queryClass), session); | |
} | |
return convertToCastDescriptor(rootDescriptor, session);//support casting | |
} | |
/** | |
* INTERNAL: | |
* For debug printing purposes. | |
*/ | |
public void writeDescriptionOn(BufferedWriter writer) throws IOException { | |
String className; | |
if (getQueryClass() == null) { | |
className = "QUERY OBJECT"; | |
} else { | |
className = getQueryClass().getName(); | |
} | |
writer.write(className + tableAliasesDescription()); | |
} | |
} |