/******************************************************************************* | |
* 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.internal.jpa.parsing; | |
import java.util.*; | |
import org.eclipse.persistence.expressions.*; | |
import org.eclipse.persistence.queries.*; | |
import org.eclipse.persistence.internal.localization.*; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
/** | |
* INTERNAL | |
* <p><b>Purpose</b>: A ParseTree contains Node(s). This contains a root Node and provides traversal utilities. | |
* <p><b>Responsibilities</b>:<ul> | |
* <li> Add parameters to the query | |
* <li> Generate an expression for the query | |
* <li> Answer true if the tree has parameters | |
* <li> Maintain the primary class name for the query | |
* <li> Maintain the root of the parse tree | |
* <li> Maintain the context for the parse tree | |
* <li> Maintain the distinct state for the parse tree | |
* <li> Print the contents of the parse tree on a string | |
* </ul> | |
* @author Jon Driscoll and Joel Lucuik | |
* @since TopLink 4.0 | |
*/ | |
public class ParseTree { | |
private ParseTreeContext context; | |
private QueryNode queryNode; | |
private FromNode fromNode; | |
private SetNode setNode; | |
private WhereNode whereNode; | |
private OrderByNode orderByNode = null; | |
private GroupByNode groupByNode = null; | |
private HavingNode havingNode = null; | |
private ClassLoader classLoader = null; | |
private short distinctState = ObjectLevelReadQuery.UNCOMPUTED_DISTINCT; | |
private boolean validated = false; | |
private Set unusedVariables = null; | |
/** | |
* Return a new ParseTree. | |
*/ | |
public ParseTree() { | |
super(); | |
} | |
/** | |
* INTERNAL | |
* Returns a DatabaseQuery instance for this ParseTree. | |
*/ | |
public DatabaseQuery createDatabaseQuery() { | |
return (queryNode == null) ? null : | |
queryNode.createDatabaseQuery(context); | |
} | |
/** | |
* INTERNAL | |
* Adjust the reference class of the passed query if necessary | |
* | |
* Need to test this for Employee, employee.getAddress(), report query | |
*/ | |
public void adjustReferenceClassForQuery(DatabaseQuery theQuery, GenerationContext generationContext) { | |
Class referenceClass = getReferenceClass(theQuery, generationContext); | |
if ((referenceClass != null) && (referenceClass != theQuery.getReferenceClass())) { | |
if (theQuery.isObjectLevelReadQuery()) { | |
// The referenceClass needs to be changed. | |
// This should only happen in an ejbSelect... | |
((ObjectLevelReadQuery)theQuery).setReferenceClass(referenceClass); | |
generationContext.setBaseQueryClass(referenceClass); | |
((ObjectLevelReadQuery)theQuery).changeDescriptor(generationContext.getSession()); | |
} else if (theQuery.isUpdateAllQuery()) { | |
((UpdateAllQuery)theQuery).setReferenceClass(referenceClass); | |
} else if (theQuery.isDeleteAllQuery()) { | |
((DeleteAllQuery)theQuery).setReferenceClass(referenceClass); | |
} | |
} | |
} | |
/** | |
* INTERNAL | |
* Initialize the base expression in the generation context. | |
*/ | |
public void initBaseExpression(ObjectLevelReadQuery theQuery, GenerationContext generationContext) { | |
String variable = getFromNode().getFirstVariable(); | |
ParseTreeContext context = generationContext.getParseTreeContext(); | |
if (context.isRangeVariable(variable)) { | |
Class referenceClass = theQuery.getReferenceClass(); | |
// Create a new expression builder for the reference class | |
ExpressionBuilder builder = new ExpressionBuilder(referenceClass); | |
// Use the expression builder as the default expression builder for the query | |
theQuery.setExpressionBuilder(builder); | |
// Add the expression builder to the expression cache in the context | |
generationContext.setBaseExpression(variable, builder); | |
} else { | |
// Get the declaring node for the variable | |
Node path = context.pathForVariable(variable); | |
// Get the ExpressionBuilder of the range variable for the path | |
Class baseClass = getBaseExpressionClass(path, generationContext); | |
// and change the reference class accordingly | |
theQuery.setReferenceClass(baseClass); | |
theQuery.changeDescriptor(generationContext.getSession()); | |
generationContext.setBaseQueryClass(baseClass); | |
// Set the node expression as base expression | |
Expression baseExpression = path.generateExpression(generationContext); | |
generationContext.setBaseExpression(variable, baseExpression); | |
// Use the base ExpressionBuilder as the default for the query | |
theQuery.setExpressionBuilder(baseExpression.getBuilder()); | |
} | |
} | |
/** | |
* INTERNAL | |
* Initialize the base expression in the generation context. | |
*/ | |
public void initBaseExpression(ModifyAllQuery theQuery, GenerationContext generationContext) { | |
ModifyNode queryNode = (ModifyNode)getQueryNode(); | |
String variable = queryNode.getCanonicalAbstractSchemaIdentifier(); | |
Class referenceClass = theQuery.getReferenceClass(); | |
// Create a new expression builder for the reference class | |
ExpressionBuilder builder = new ExpressionBuilder(referenceClass); | |
// Use the expression builder as the default expression builder for the query | |
theQuery.setExpressionBuilder(builder); | |
// Add the expression builder to the expression cache in the context | |
generationContext.setBaseExpression(variable, builder); | |
} | |
/** */ | |
private Class getBaseExpressionClass(Node node, GenerationContext generationContext) { | |
ParseTreeContext context = generationContext.getParseTreeContext(); | |
Class clazz = null; | |
if (node == null) { | |
clazz = null; | |
} else if (node.isDotNode()) { | |
// DotNode: delegate to left | |
clazz = getBaseExpressionClass(node.getLeft(), generationContext); | |
} else if (node.isVariableNode()) { | |
// VariableNode | |
String variable = ((VariableNode)node).getCanonicalVariableName(); | |
if (!context.isRangeVariable(variable)) { | |
Node path = context.pathForVariable(variable); | |
// Variable is defined in JOIN/IN clause => | |
// return the Class from its definition | |
clazz = getBaseExpressionClass(path, generationContext); | |
} else { | |
// Variable is defined in range variable decl => | |
// return its class | |
String schema = context.schemaForVariable(variable); | |
if (schema != null) { | |
clazz = context.classForSchemaName(schema, generationContext); | |
} | |
} | |
} | |
return clazz; | |
} | |
/** | |
* INTERNAL | |
* Validate the parse tree. | |
*/ | |
protected void validate(AbstractSession session, ClassLoader classLoader) { | |
validate(new TypeHelperImpl(session, classLoader)); | |
} | |
/** | |
* INTERNAL | |
* Validate the parse tree. | |
*/ | |
public void validate(TypeHelper typeHelper) { | |
ParseTreeContext context = getContext(); | |
context.setTypeHelper(typeHelper); | |
validate(context); | |
} | |
/** | |
* INTERNAL | |
* Validate the parse tree. | |
*/ | |
public void validate(ParseTreeContext context) { | |
if (validated) { | |
// already validated => return | |
return; | |
} | |
validated = true; | |
context.enterScope(); | |
if (fromNode != null) { | |
fromNode.validate(context); | |
} | |
queryNode.validate(context); | |
qualifyAttributeAccess(context); | |
if (setNode != null) { | |
setNode.validate(context); | |
} | |
if (whereNode != null) { | |
whereNode.validate(context); | |
} | |
if (hasOrderBy()) { | |
orderByNode.validate(context, (SelectNode)queryNode); | |
} | |
if (hasGroupBy()) { | |
groupByNode.validate(context, (SelectNode)queryNode); | |
} | |
if (hasHaving()) { | |
havingNode.validate(context, groupByNode); | |
} | |
// store the set od unused variable for later use | |
unusedVariables = context.getUnusedVariables(); | |
context.leaveScope(); | |
} | |
/** | |
* INTERNAL | |
* This method handles any unqualified field access in bulk UPDATE and | |
* DELETE statements. A UPDATE or DELETE statement may not define an | |
* identification variable. In this case any field accessed from the | |
* current class is not qualified with an identification variable, e.g. | |
* UPDATE Customer SET name = :newname | |
* The method goes through the expressions of the SET clause and the WHERE | |
* clause of such an DELETE and UPDATE statement and qualifies the field | |
* access using the abstract schema name as qualifier. | |
*/ | |
protected void qualifyAttributeAccess(ParseTreeContext context) { | |
if ((queryNode.isUpdateNode() || queryNode.isDeleteNode()) && | |
((ModifyNode)queryNode).getAbstractSchemaIdentifier() == null) { | |
if (setNode != null) { | |
setNode.qualifyAttributeAccess(context); | |
} | |
if (whereNode != null) { | |
whereNode.qualifyAttributeAccess(context); | |
} | |
} | |
} | |
/** | |
* INTERNAL | |
* Add the ordering to the passed query | |
*/ | |
public void addOrderingToQuery(ObjectLevelReadQuery theQuery, GenerationContext generationContext) { | |
if (hasOrderBy()) { | |
(getOrderByNode()).addOrderingToQuery(theQuery, generationContext); | |
} | |
} | |
/** | |
* INTERNAL | |
* Add the grouping to the passed query | |
*/ | |
public void addGroupingToQuery(ObjectLevelReadQuery theQuery, GenerationContext generationContext) { | |
if (hasGroupBy()) { | |
(getGroupByNode()).addGroupingToQuery(theQuery, generationContext); | |
} | |
} | |
/** | |
* INTERNAL | |
* Add the having to the passed query | |
*/ | |
public void addHavingToQuery(ObjectLevelReadQuery theQuery, GenerationContext generationContext) { | |
if (hasHaving()) { | |
(getHavingNode()).addHavingToQuery(theQuery, generationContext); | |
} | |
} | |
/** | |
* INTERNAL | |
*/ | |
public void addNonFetchJoinAttributes(ObjectLevelReadQuery theQuery, GenerationContext generationContext) { | |
ParseTreeContext context = generationContext.getParseTreeContext(); | |
for (Iterator i = unusedVariables.iterator(); i.hasNext();) { | |
String variable = (String)i.next(); | |
Expression expr = null; | |
if (!context.isRangeVariable(variable)) { | |
Node path = context.pathForVariable(variable); | |
expr = path.generateExpression(generationContext); | |
theQuery.addNonFetchJoinedAttribute(expr); | |
} else { | |
// Ignore, assume 'Select 1 from Employee e' or sorts | |
// unused range variable => not supported yet | |
//throw JPQLException.notYetImplemented(context.getQueryInfo(), | |
// "Variable [" + variable + "] is defined in a range variable declaration, but not used in the rest of the query."); | |
} | |
} | |
} | |
/** | |
* INTERNAL | |
* Add the updates to the passed query | |
*/ | |
public void addUpdatesToQuery(UpdateAllQuery theQuery, GenerationContext generationContext) { | |
if (getSetNode() != null) { | |
(getSetNode()).addUpdatesToQuery(theQuery, generationContext); | |
} | |
} | |
/** | |
* INTERNAL | |
* Add parameters to the query | |
*/ | |
public void addParametersToQuery(DatabaseQuery query) { | |
//Bug#4646580 Add arguments to query | |
if (context.hasParameters()) { | |
TypeHelper typeHelper = context.getTypeHelper(); | |
for (Iterator i = context.getParameterNames().iterator(); i.hasNext();) { | |
String param = (String)i.next(); | |
Object type = context.getParameterType(param); | |
Class clazz = typeHelper.getJavaClass(type); | |
if (clazz == null) { | |
clazz = Object.class; | |
} | |
query.addArgument(param, clazz); | |
} | |
} | |
} | |
/** | |
* INTERNAL | |
* Apply the select or update to the passed query. | |
* If there is a single attribute being selected, add it to the query result set | |
* If an aggregate is being used, add it to the query result set | |
*/ | |
public void applyQueryNodeToQuery(DatabaseQuery theQuery, GenerationContext generationContext) { | |
getQueryNode().applyToQuery(theQuery, generationContext); | |
} | |
/** | |
* INTERNAL | |
* Build the context to be used when generating the expression from the parse tree | |
*/ | |
public GenerationContext buildContext(DatabaseQuery query, AbstractSession sessionForContext) { | |
if (query.isObjectLevelReadQuery()) { | |
return buildContextForReadQuery(sessionForContext); | |
} else if (query.isUpdateAllQuery() || query.isDeleteAllQuery()) { | |
return new GenerationContext(getContext(), sessionForContext, this); | |
} | |
return null; | |
} | |
/** | |
* INTERNAL | |
* Build the context to be used when generating the expression from the parse tree | |
*/ | |
public GenerationContext buildContextForReadQuery(AbstractSession sessionForContext) { | |
return new SelectGenerationContext(getContext(), sessionForContext, this); | |
} | |
/** | |
* INTERNAL | |
* Build a context for the expression generation | |
*/ | |
public Expression generateExpression(DatabaseQuery readQuery, GenerationContext generationContext) { | |
Expression selectExpression = getQueryNode().generateExpression(generationContext); | |
if (getWhereNode() == null) { | |
return selectExpression; | |
} | |
Expression whereExpression = getWhereNode().generateExpression(generationContext); | |
selectExpression = getQueryNode().generateExpression(generationContext); | |
if (selectExpression != null) { | |
whereExpression = selectExpression.and(whereExpression); | |
} | |
return whereExpression; | |
} | |
/** | |
* Return the context for this parse tree | |
*/ | |
public ParseTreeContext getContext() { | |
return context; | |
} | |
/** | |
* INTERNAL | |
* Return the FROM Node | |
*/ | |
public FromNode getFromNode() { | |
return fromNode; | |
} | |
/** | |
* INTERNAL | |
* Return a class loader | |
* @return java.lang.ClassLoader | |
*/ | |
public ClassLoader getClassLoader() { | |
if (classLoader == null) { | |
return org.eclipse.persistence.internal.helper.ConversionManager.getDefaultManager().getLoader(); | |
} else { | |
return classLoader; | |
} | |
} | |
/** | |
* INTERNAL | |
* Return the OrderByNode | |
*/ | |
public OrderByNode getOrderByNode() { | |
return orderByNode; | |
} | |
/** | |
* INTERNAL | |
* Return the GroupByNode | |
*/ | |
public GroupByNode getGroupByNode() { | |
return groupByNode; | |
} | |
/** | |
* INTERNAL | |
* Return the HavingNode | |
*/ | |
public HavingNode getHavingNode() { | |
return havingNode; | |
} | |
/** | |
* getReferenceClass(): Answer the class which will be the reference class for the query. | |
* Resolve this using the node parsed from the "SELECT" of the EJBQL query string | |
*/ | |
public Class getReferenceClass(DatabaseQuery query, GenerationContext generationContext) { | |
if (getQueryNode() == null) { | |
return null; | |
} | |
return getQueryNode().getReferenceClass(generationContext); | |
} | |
/** | |
* INTERNAL | |
* Return the root node for the tree | |
*/ | |
public QueryNode getQueryNode() { | |
return queryNode; | |
} | |
/** | |
* INTERNAL | |
* Return the set node for the tree | |
*/ | |
public SetNode getSetNode() { | |
return setNode; | |
} | |
/** | |
* INTERNAL | |
* Return the Where node | |
*/ | |
public WhereNode getWhereNode() { | |
return whereNode; | |
} | |
/** | |
* INTERNAL | |
* Return the DISTINCT state for the tree | |
*/ | |
public short getDistinctState() { | |
return distinctState; | |
} | |
/** | |
* INTERNAL | |
* Does this EJBQL have an Ordering Clause | |
*/ | |
public boolean hasOrderBy() { | |
return getOrderByNode() != null; | |
} | |
/** | |
* INTERNAL | |
* Does this EJBQL have a Grouping Clause | |
*/ | |
public boolean hasGroupBy() { | |
return getGroupByNode() != null; | |
} | |
/** | |
* INTERNAL | |
* Does this EJBQL have a Having Clause | |
*/ | |
public boolean hasHaving() { | |
return getHavingNode() != null; | |
} | |
/** | |
* INTERNAL: | |
* Set the class loader for this parse tree | |
* @param loader | |
*/ | |
public void setClassLoader(ClassLoader loader){ | |
this.classLoader = loader; | |
} | |
/** | |
* INTERNAL | |
* Set the context for this parse tree | |
*/ | |
public void setContext(ParseTreeContext newContext) { | |
context = newContext; | |
} | |
/** | |
* INTERNAL | |
* Set the FROM node for the query | |
*/ | |
public void setFromNode(FromNode fromNode) { | |
this.fromNode = fromNode; | |
} | |
/** | |
* INTERNAL | |
* Set the Order by node | |
*/ | |
public void setOrderByNode(OrderByNode newOrderByNode) { | |
orderByNode = newOrderByNode; | |
} | |
/** | |
* INTERNAL | |
* Set the Group by node | |
*/ | |
public void setGroupByNode(GroupByNode newGroupByNode) { | |
groupByNode = newGroupByNode; | |
} | |
/** | |
* INTERNAL | |
* Set the Having node | |
*/ | |
public void setHavingNode(HavingNode newHavingNode) { | |
havingNode = newHavingNode; | |
} | |
public void setSelectionCriteriaForQuery(DatabaseQuery theQuery, GenerationContext generationContext) { | |
theQuery.setSelectionCriteria(generateExpression(theQuery, generationContext)); | |
} | |
/** | |
* INTERNAL | |
* Set the Select node | |
*/ | |
public void setQueryNode(QueryNode newQueryNode) { | |
queryNode = newQueryNode; | |
} | |
/** | |
* INTERNAL | |
* Set the Where node | |
*/ | |
public void setSetNode(SetNode newSetNode) { | |
setNode = newSetNode; | |
} | |
/** | |
* INTERNAL | |
* Set the Where node | |
*/ | |
public void setWhereNode(WhereNode newWhereNode) { | |
whereNode = newWhereNode; | |
} | |
/** | |
* INTERNAL | |
* Set the DISTINCT state for the tree | |
*/ | |
public void setDistinctState(short newDistinctState) { | |
distinctState = newDistinctState; | |
} | |
/** | |
* INTERNAL | |
* Print the contents of the parse tree on a string | |
*/ | |
public String toString() { | |
StringBuffer buffer = new StringBuffer(); | |
buffer.append(getContext().toString()); | |
return ToStringLocalization.buildMessage("context", (Object[])null) + " " + buffer.toString(); | |
} | |
/** | |
* INTERNAL | |
* Verify that the alias in the SELECT is valid. | |
* Invalid: SELECT OBJECT(badAlias) FROM Employee employee.... | |
* Valid: SELECT OBJECT(employee) FROM Employee employee.... | |
*/ | |
public void verifySelect(DatabaseQuery theQuery, GenerationContext generationContext) { | |
if (theQuery.isObjectLevelReadQuery()) { | |
//verify the selected alias, | |
//this will throw an error if the alias is bad | |
((SelectNode)getQueryNode()).verifySelectedAlias(generationContext); | |
} | |
} | |
/** | |
* INTERNAL | |
* Answer true if DISTINCT has been chosen. | |
*/ | |
public boolean usesDistinct() { | |
return distinctState == ObjectLevelReadQuery.USE_DISTINCT; | |
} | |
} |