All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.jpa.jpql.tools.resolver.DeclarationResolver Maven / Gradle / Ivy

There is a newer version: 5.0.0-B02
Show newest version
/*******************************************************************************
 * Copyright (c) 2006, 2014 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
 *
 ******************************************************************************/
package org.eclipse.persistence.jpa.jpql.tools.resolver;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.LiteralType;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName;
import org.eclipse.persistence.jpa.jpql.parser.AnonymousExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.DeleteClause;
import org.eclipse.persistence.jpa.jpql.parser.DeleteStatement;
import org.eclipse.persistence.jpa.jpql.parser.Expression;
import org.eclipse.persistence.jpa.jpql.parser.FromClause;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.JPQLExpression;
import org.eclipse.persistence.jpa.jpql.parser.Join;
import org.eclipse.persistence.jpa.jpql.parser.NullExpression;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.ResultVariable;
import org.eclipse.persistence.jpa.jpql.parser.SelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.SimpleFromClause;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.SubExpression;
import org.eclipse.persistence.jpa.jpql.parser.UpdateClause;
import org.eclipse.persistence.jpa.jpql.parser.UpdateStatement;
import org.eclipse.persistence.jpa.jpql.tools.JPQLQueryContext;
import org.eclipse.persistence.jpa.jpql.tools.spi.IMapping;
import org.eclipse.persistence.jpa.jpql.tools.spi.IQuery;
import org.eclipse.persistence.jpa.jpql.tools.spi.IType;
import org.eclipse.persistence.jpa.jpql.tools.spi.ITypeDeclaration;

/**
 * This {@link Resolver} is responsible to visit the current query (which is either the top-level
 * query or a subquery) and gathers the information from the declaration clause. For a SELECT
 * or DELETE clause, the information will be retrieved from the FROM clause. For an
 * UDPATE clause, it will be retrieved from the unique identification range variable
 * declaration.
 *
 * @version 2.5
 * @since 2.3
 * @author Pascal Filion
 */
public class DeclarationResolver extends Resolver {

	/**
	 * The {@link Declaration Declarations} of the current query that was parsed.
	 */
	private List declarations;

	/**
	 * This visitor is responsible to visit the current query's declaration and populate this
	 * resolver with the list of declarations.
	 */
	private DeclarationVisitor declarationVisitor;

	/**
	 * This visitor is responsible to convert the abstract schema name into a path expression.
	 */
	private QualifyRangeDeclarationVisitor qualifyRangeDeclarationVisitor;

	/**
	 * The context used to query information about the query.
	 */
	private JPQLQueryContext queryContext;

	/**
	 * The identification variable names mapped to their resolvers.
	 */
	private Map resolvers;

	/**
	 * The variables identifying the select expressions, if any was defined or an empty set if none
	 * were defined.
	 */
	private Map resultVariables;

	/**
	 * This visitor resolves the "root" object represented by the given {@link Expression}. This will
	 * also handle using a subquery in the FROM clause. This is only supported by
	 * EclipseLink.
	 */
	private RootObjectExpressionVisitor rootObjectExpressionVisitor;

	/**
	 * Creates a new DeclarationResolver.
	 *
	 * @param parent The parent resolver if this is used for a subquery or null if it's used for the
	 * top-level query
	 * @param queryContext The context used to query information about the query
	 */
	public DeclarationResolver(DeclarationResolver parent, JPQLQueryContext queryContext) {
		super(parent);
		initialize(queryContext);
	}

	/**
	 * Adds the given {@link Declaration} at the end of the list.
	 *
	 * @param declaration The {@link Declaration} representing a single variable declaration
	 */
	protected final void addDeclaration(Declaration declaration) {
		declarations.add(declaration);
	}

	/**
	 * Registers a range variable declaration that will be used when a JPQL fragment is parsed.
	 *
	 * @param entityName The name of the entity to be accessible with the given variable name
	 * @param variableName The identification variable used to navigate to the entity
	 */
	public void addRangeVariableDeclaration(String entityName, String variableName) {

		// Always make the identification variable be upper case since it's
		// case insensitive, the get will also use upper case
		String internalVariableName = variableName.toUpperCase();

		// Make sure the identification variable was not declared more than once,
		// this could cause issues when trying to resolve it
		if (!resolvers.containsKey(internalVariableName)) {

			// Resolve the expression and map it with the identification variable
			Resolver resolver = new EntityResolver(this, entityName);
			resolver = new IdentificationVariableResolver(resolver, variableName);
			resolvers.put(internalVariableName, resolver);

			RangeDeclaration declaration = new RangeDeclaration();
			declaration.rootPath               = entityName;
			declaration.identificationVariable = new IdentificationVariable(null, variableName);
			declarations.add(declaration);
		}
	}

	protected DeclarationVisitor buildDeclarationVisitor() {
		return new DeclarationVisitor();
	}

	protected RootObjectExpressionVisitor buildRootObjectExpressionVisitor() {
		return new RootObjectExpressionVisitor();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected IType buildType() {
		return getTypeHelper().unknownType();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected ITypeDeclaration buildTypeDeclaration() {
		return getTypeHelper().unknownTypeDeclaration();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void checkParent(Resolver parent) {
		// Don't do anything, this is the root
	}

	/**
	 * Converts the given {@link Declaration} from being set as a range variable declaration to
	 * a path expression declaration.
	 * 

* In this query "UPDATE Employee SET firstName = 'MODIFIED' WHERE (SELECT COUNT(m) FROM * managedEmployees m) {@literal >} 0" managedEmployees is an unqualified * collection-valued path expression (employee.managedEmployees). * * @param declaration The {@link Declaration} that was parsed to range over an abstract schema * name but is actually ranging over a path expression * @param outerVariableName The identification variable coming from the parent identification * variable declaration */ public void convertUnqualifiedDeclaration(Declaration declaration, String outerVariableName) { QualifyRangeDeclarationVisitor visitor = qualifyRangeDeclarationVisitor(); try { visitor.oldDeclaration = declaration; visitor.outerVariableName = outerVariableName; declaration.declarationExpression.accept(visitor); } finally { visitor.oldDeclaration = null; visitor.newDeclaration = null; visitor.outerVariableName = null; } } /** * Disposes the internal data. */ public void dispose() { resolvers .clear(); declarations .clear(); resultVariables.clear(); } /** * Retrieves the {@link Declaration} for which the given variable name is used to navigate to the * "root" object. * * @param variableName The name of the identification variable that is used to navigate a "root" object * @return The {@link Declaration} containing the information about the identification variable declaration * @since 2.5 */ public Declaration getDeclaration(String variableName) { for (Declaration declaration : declarations) { if (declaration.getVariableName().equalsIgnoreCase(variableName)) { return declaration; } } return null; } /** * Returns the ordered list of {@link Declaration Declarations}. * * @return The {@link Declaration Declarations} of the current query that was parsed */ public List getDeclarations() { return declarations; } protected DeclarationVisitor getDeclarationVisitor() { if (declarationVisitor == null) { declarationVisitor = buildDeclarationVisitor(); } return declarationVisitor; } /** * {@inheritDoc} */ @Override public DeclarationResolver getParent() { return (DeclarationResolver) super.getParent(); } /** * {@inheritDoc} */ @Override public IQuery getQuery() { return queryContext.getQuery(); } /** * Returns the {@link JPQLQueryContext} that is used by this visitor. * * @return The {@link JPQLQueryContext} holding onto the JPQL query and the cached information */ protected JPQLQueryContext getQueryContext() { return queryContext; } /** * Retrieves the {@link Resolver} mapped with the given identification variable. If the * identification is not defined in the declaration traversed by this resolver, than the search * will traverse the parent hierarchy. * * @param variableName The identification variable that maps a {@link Resolver} * @return The {@link Resolver} mapped with the given identification variable */ public Resolver getResolver(String variableName) { variableName = variableName.toUpperCase(); Resolver resolver = getResolverImp(variableName); if ((resolver == null) && (getParent() != null)) { resolver = getParent().getResolver(variableName); } if (resolver == null) { resolver = new NullResolver(this); resolvers.put(variableName, resolver); } return resolver; } /** * Retrieves the {@link Resolver} mapped with the given identification variable. The search does * not traverse the parent hierarchy. * * @param variableName The identification variable that maps a {@link Resolver} * @return The {@link Resolver} mapped with the given identification variable */ protected Resolver getResolverImp(String variableName) { return resolvers.get(variableName); } /** * Returns the variables that got defined in the select expression. This only applies to JPQL * queries built for JPA 2.0 or later. * * @return The variables identifying the select expressions, if any was defined or an empty set * if none were defined */ public Set getResultVariables() { return new HashSet(resultVariables.values()); } /** * Returns the map of result variables that got used to define a select expression. This only * applies to JPQL queries built for JPA 2.0. * * @return The variables identifying the select expressions, if any was defined or an empty map * if none were defined */ public Map getResultVariablesMap() { return resultVariables; } protected RootObjectExpressionVisitor getRootObjectExpressionVisitor() { if (rootObjectExpressionVisitor == null) { rootObjectExpressionVisitor = buildRootObjectExpressionVisitor(); } return rootObjectExpressionVisitor; } /** * Determines whether the JPQL expression has JOIN expressions. * * @return true if the query or subquery being traversed contains JOIN * expressions; false otherwise */ public boolean hasJoins() { for (Declaration declaration : declarations) { if (declaration.hasJoins()) { return true; } } return false; } /** * Initializes this DeclarationResolver. * * @param queryContext The context used to query information about the query */ protected void initialize(JPQLQueryContext queryContext) { this.queryContext = queryContext; this.resolvers = new HashMap(); this.declarations = new LinkedList(); this.resultVariables = new HashMap(); } /** * Determines whether the given identification variable is defining a JOIN expression or * in a IN expressions for a collection-valued field. If the search didn't find the * identification in this resolver, then it will traverse the parent hierarchy. * * @param variableName The identification variable to check for what it maps * @return true if the given identification variable maps a collection-valued field * defined in a JOIN or IN expression; false otherwise */ public boolean isCollectionIdentificationVariable(String variableName) { boolean result = isCollectionIdentificationVariableImp(variableName); if (!result && (getParent() != null)) { result = getParent().isCollectionIdentificationVariableImp(variableName); } return result; } /** * Determines whether the given identification variable is defining a JOIN expression or * in a IN expressions for a collection-valued field. The search does not traverse * the parent hierarchy. * * @param variableName The identification variable to check for what it maps * @return true if the given identification variable maps a collection-valued field * defined in a JOIN or IN expression; false otherwise */ protected boolean isCollectionIdentificationVariableImp(String variableName) { for (Declaration declaration : declarations) { switch (declaration.getType()) { case COLLECTION: { if (declaration.getVariableName().equalsIgnoreCase(variableName)) { return true; } return false; } case RANGE: case DERIVED: { AbstractRangeDeclaration rangeDeclaration = (AbstractRangeDeclaration) declaration; // Check the JOIN expressions for (Join join : rangeDeclaration.getJoins()) { String joinVariableName = queryContext.literal( join.getIdentificationVariable(), LiteralType.IDENTIFICATION_VARIABLE ); if (joinVariableName.equalsIgnoreCase(variableName)) { // Make sure the JOIN expression maps a collection mapping Resolver resolver = getResolver(variableName); IMapping mapping = (resolver != null) ? resolver.getMapping() : null; return (mapping != null) && mapping.isCollection(); } } } } } return false; } /** * Determines whether the given variable name is an identification variable name mapping an * entity. The search traverses the parent resolver if it is not found in this resolver, which * represents the current JPQL query. * * @param variableName The name of the variable to verify if it's defined in a range variable * declaration in the current query or any parent query * @return true if the variable name is mapping an abstract schema name; false * if it's defined in a collection member declaration */ public boolean isRangeIdentificationVariable(String variableName) { boolean result = isRangeIdentificationVariableImp(variableName); if (!result && (getParent() != null)) { result = getParent().isRangeIdentificationVariableImp(variableName); } return result; } /** * Determines whether the given variable name is an identification variable name mapping an * entity. The search only searches in this resolver, which represents the current JPQL query. * * @param variableName The name of the variable to verify if it's defined in a range variable * declaration in the current query or any parent query * @return true if the variable name is mapping an abstract schema name; false * if it's defined in a collection member declaration */ protected boolean isRangeIdentificationVariableImp(String variableName) { for (Declaration declaration : declarations) { if (declaration.getType().isRange() && declaration.getVariableName().equalsIgnoreCase(variableName)) { return true; } } return false; } /** * Determines if the given variable is a result variable. * * @param variable The variable to check if it's a result variable * @return true if the given variable is defined as a result variable; false * otherwise */ public boolean isResultVariable(String variable) { return resultVariables.containsValue(variable.toUpperCase()); } /** * Visits the current query (which is either the top-level query or a subquery) and gathers the * information from the declaration clause. * * @param expression The {@link Expression} to visit in order to retrieve the information * contained in the given query's declaration */ public void populate(Expression expression) { DeclarationVisitor visitor = getDeclarationVisitor(); try { expression.accept(visitor); } finally { visitor.currentDeclaration = null; } } protected QualifyRangeDeclarationVisitor qualifyRangeDeclarationVisitor() { if (qualifyRangeDeclarationVisitor == null) { qualifyRangeDeclarationVisitor = new QualifyRangeDeclarationVisitor(); } return qualifyRangeDeclarationVisitor; } /** * Resolves the "root" object represented by the given {@link Expression}. This will also handle * using a subquery in the FROM clause. This is only supported by EclipseLink. * * @param expression The {@link Expression} to visit, which represents the "root" object of a * declaration * @return The {@link Resolver} for the given {@link Expression} */ protected Resolver resolveRootObject(Expression expression) { RootObjectExpressionVisitor visitor = getRootObjectExpressionVisitor(); try { expression.accept(visitor); return visitor.resolver; } finally { visitor.resolver = null; } } protected String visitDeclaration(Expression expression, Expression identificationVariable) { // Visit the identification variable expression and retrieve the identification variable name String variableName = queryContext.literal( identificationVariable, LiteralType.IDENTIFICATION_VARIABLE ); // If it's not empty, then we can create a Resolver if (variableName != ExpressionTools.EMPTY_STRING) { // Always make the identification variable be upper case since it's // case insensitive, the get will also use upper case String internalVariableName = variableName.toUpperCase(); // Make sure the identification variable was not declared more than once, // this could cause issues when trying to resolve it if (!resolvers.containsKey(internalVariableName)) { // Resolve the expression and map it with the identification variable Resolver resolver = resolveRootObject(expression); resolver = new IdentificationVariableResolver(resolver, variableName); resolvers.put(internalVariableName, resolver); } return variableName; } return ExpressionTools.EMPTY_STRING; } protected class DeclarationVisitor extends AbstractExpressionVisitor { /** * This flag is used to determine what to do in {@link #visit(SimpleSelectStatement)}. */ protected boolean buildingDeclaration; /** * Flag used to determine if the {@link IdentificationVariable} to visit is a result variable. */ protected boolean collectResultVariable; /** * The {@link Declaration} being populated. */ protected Declaration currentDeclaration; /** * {@inheritDoc} */ @Override public void visit(AbstractSchemaName expression) { currentDeclaration = new RangeDeclaration(); currentDeclaration.rootPath = expression.getText(); declarations.add(currentDeclaration); } /** * {@inheritDoc} */ @Override public void visit(CollectionExpression expression) { expression.acceptChildren(this); } /** * {@inheritDoc} */ @Override public void visit(CollectionMemberDeclaration expression) { CollectionDeclaration declaration = new CollectionDeclaration(); declaration.declarationExpression = expression; declaration.baseExpression = expression.getCollectionValuedPathExpression(); declaration.rootPath = declaration.baseExpression.toActualText(); declarations.add(declaration); // Retrieve the IdentificationVariable Expression identificationVariable = expression.getIdentificationVariable(); String variableName = visitDeclaration(expression, identificationVariable); if (variableName != ExpressionTools.EMPTY_STRING) { declaration.identificationVariable = (IdentificationVariable) identificationVariable; } } /** * {@inheritDoc} */ @Override public void visit(CollectionValuedPathExpression expression) { currentDeclaration = new DerivedDeclaration(); currentDeclaration.rootPath = expression.toActualText(); declarations.add(currentDeclaration); } /** * {@inheritDoc} */ @Override public void visit(DeleteClause expression) { try { buildingDeclaration = true; expression.getRangeVariableDeclaration().accept(this); buildingDeclaration = false; currentDeclaration.declarationExpression = expression; } finally { currentDeclaration = null; } } /** * {@inheritDoc} */ @Override public void visit(DeleteStatement expression) { expression.getDeleteClause().accept(this); } /** * {@inheritDoc} */ @Override public void visit(FromClause expression) { expression.getDeclaration().accept(this); } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariable expression) { if (collectResultVariable) { resultVariables.put(expression, expression.getText().toUpperCase()); } } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariableDeclaration expression) { try { expression.getRangeVariableDeclaration().accept(this); expression.getJoins().accept(this); currentDeclaration.declarationExpression = expression; } finally { currentDeclaration = null; } } /** * {@inheritDoc} */ @Override public void visit(Join expression) { AbstractRangeDeclaration rangeDeclaration = (AbstractRangeDeclaration) currentDeclaration; Expression identificationVariable = expression.getIdentificationVariable(); visitDeclaration(expression, identificationVariable); rangeDeclaration.addJoin(expression); } /** * {@inheritDoc} */ @Override public void visit(JPQLExpression expression) { expression.getQueryStatement().accept(this); } /** * {@inheritDoc} */ @Override public void visit(NullExpression expression) { if (buildingDeclaration) { currentDeclaration = new UnknownDeclaration(); currentDeclaration.baseExpression = expression; currentDeclaration.rootPath = ExpressionTools.EMPTY_STRING; declarations.add(currentDeclaration); } } /** * {@inheritDoc} */ @Override public void visit(RangeVariableDeclaration expression) { // Visit the "root" object, which will create the right Declaration Expression rootObject = expression.getRootObject(); buildingDeclaration = true; rootObject.accept(this); buildingDeclaration = false; // Set the base expression currentDeclaration.baseExpression = expression; // Set the identification variable Expression identificationVariable = expression.getIdentificationVariable(); String variableName = visitDeclaration(rootObject, identificationVariable); if (variableName != ExpressionTools.EMPTY_STRING) { currentDeclaration.identificationVariable = (IdentificationVariable) identificationVariable; } } /** * {@inheritDoc} */ @Override public void visit(ResultVariable expression) { collectResultVariable = true; expression.getResultVariable().accept(this); collectResultVariable = false; } /** * {@inheritDoc} */ @Override public void visit(SelectClause expression) { expression.getSelectExpression().accept(this); } /** * {@inheritDoc} */ @Override public void visit(SelectStatement expression) { expression.getFromClause().accept(this); expression.getSelectClause().accept(this); } /** * {@inheritDoc} */ @Override public void visit(SimpleFromClause expression) { expression.getDeclaration().accept(this); } /** * {@inheritDoc} */ @Override public void visit(SimpleSelectClause expression) { expression.getSelectExpression().accept(this); } /** * {@inheritDoc} */ @Override public void visit(SimpleSelectStatement expression) { expression.getFromClause().accept(this); } /** * {@inheritDoc} */ @Override public void visit(UpdateClause expression) { try { buildingDeclaration = true; expression.getRangeVariableDeclaration().accept(this); buildingDeclaration = false; currentDeclaration.declarationExpression = expression; } finally { currentDeclaration = null; } } /** * {@inheritDoc} */ @Override public void visit(UpdateStatement expression) { expression.getUpdateClause().accept(this); } } protected class QualifyRangeDeclarationVisitor extends AbstractExpressionVisitor { /** * The new {@link Declaration}. */ protected Declaration newDeclaration; /** * The {@link Declaration} being modified. */ protected Declaration oldDeclaration; /** * The identification variable coming from the parent identification variable declaration. */ protected String outerVariableName; /** * {@inheritDoc} */ @Override public void visit(CollectionValuedPathExpression expression) { newDeclaration.rootPath = expression.toActualText(); newDeclaration.baseExpression = expression; } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariableDeclaration expression) { expression.getRangeVariableDeclaration().accept(this); } /** * {@inheritDoc} */ @Override public void visit(RangeVariableDeclaration expression) { newDeclaration = new DerivedDeclaration(); expression.setVirtualIdentificationVariable(outerVariableName, newDeclaration.rootPath); expression.getRootObject().accept(this); } } /** * This visitor takes care to support a subquery defined as a "root" object. */ protected class RootObjectExpressionVisitor extends AnonymousExpressionVisitor { /** * The {@link Resolver} of the "root" object. */ protected Resolver resolver; /** * {@inheritDoc} */ @Override protected void visit(Expression expression) { resolver = queryContext.getResolver(expression); } /** * {@inheritDoc} */ @Override public void visit(SimpleSelectStatement expression) { resolver = new FromSubqueryResolver( DeclarationResolver.this, DeclarationResolver.this.queryContext, expression ); } /** * {@inheritDoc} */ @Override public void visit(SubExpression expression) { expression.getExpression().accept(this); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy