/******************************************************************************* * Copyright (c) 2006, 2015 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; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.persistence.jpa.jpql.JPQLQueryDeclaration.Type; import org.eclipse.persistence.jpa.jpql.parser.AbsExpression; import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor; import org.eclipse.persistence.jpa.jpql.parser.AbstractFromClause; import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName; import org.eclipse.persistence.jpa.jpql.parser.AbstractSingleEncapsulatedExpression; import org.eclipse.persistence.jpa.jpql.parser.AdditionExpression; import org.eclipse.persistence.jpa.jpql.parser.AllOrAnyExpression; import org.eclipse.persistence.jpa.jpql.parser.AndExpression; import org.eclipse.persistence.jpa.jpql.parser.AnonymousExpressionVisitor; import org.eclipse.persistence.jpa.jpql.parser.ArithmeticExpression; import org.eclipse.persistence.jpa.jpql.parser.ArithmeticFactor; import org.eclipse.persistence.jpa.jpql.parser.AvgFunction; import org.eclipse.persistence.jpa.jpql.parser.BadExpression; import org.eclipse.persistence.jpa.jpql.parser.BetweenExpression; import org.eclipse.persistence.jpa.jpql.parser.CaseExpression; import org.eclipse.persistence.jpa.jpql.parser.CoalesceExpression; import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression; import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberDeclaration; import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberExpression; import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpression; import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression; import org.eclipse.persistence.jpa.jpql.parser.CompoundExpression; import org.eclipse.persistence.jpa.jpql.parser.ConcatExpression; import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression; import org.eclipse.persistence.jpa.jpql.parser.CountFunction; import org.eclipse.persistence.jpa.jpql.parser.DateTime; import org.eclipse.persistence.jpa.jpql.parser.DeleteClause; import org.eclipse.persistence.jpa.jpql.parser.DeleteStatement; import org.eclipse.persistence.jpa.jpql.parser.DivisionExpression; import org.eclipse.persistence.jpa.jpql.parser.EmptyCollectionComparisonExpression; import org.eclipse.persistence.jpa.jpql.parser.EntityTypeLiteral; import org.eclipse.persistence.jpa.jpql.parser.EntryExpression; import org.eclipse.persistence.jpa.jpql.parser.ExistsExpression; import org.eclipse.persistence.jpa.jpql.parser.Expression; import org.eclipse.persistence.jpa.jpql.parser.FromClause; import org.eclipse.persistence.jpa.jpql.parser.FunctionExpression; import org.eclipse.persistence.jpa.jpql.parser.GroupByClause; import org.eclipse.persistence.jpa.jpql.parser.HavingClause; import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable; import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariableDeclaration; import org.eclipse.persistence.jpa.jpql.parser.InExpression; import org.eclipse.persistence.jpa.jpql.parser.IndexExpression; import org.eclipse.persistence.jpa.jpql.parser.InputParameter; import org.eclipse.persistence.jpa.jpql.parser.JPQLExpression; import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar; import org.eclipse.persistence.jpa.jpql.parser.Join; import org.eclipse.persistence.jpa.jpql.parser.KeyExpression; import org.eclipse.persistence.jpa.jpql.parser.KeywordExpression; import org.eclipse.persistence.jpa.jpql.parser.LengthExpression; import org.eclipse.persistence.jpa.jpql.parser.LikeExpression; import org.eclipse.persistence.jpa.jpql.parser.LocateExpression; import org.eclipse.persistence.jpa.jpql.parser.LowerExpression; import org.eclipse.persistence.jpa.jpql.parser.MaxFunction; import org.eclipse.persistence.jpa.jpql.parser.MinFunction; import org.eclipse.persistence.jpa.jpql.parser.ModExpression; import org.eclipse.persistence.jpa.jpql.parser.MultiplicationExpression; import org.eclipse.persistence.jpa.jpql.parser.NotExpression; import org.eclipse.persistence.jpa.jpql.parser.NullComparisonExpression; import org.eclipse.persistence.jpa.jpql.parser.NullExpression; import org.eclipse.persistence.jpa.jpql.parser.NullIfExpression; import org.eclipse.persistence.jpa.jpql.parser.NumericLiteral; import org.eclipse.persistence.jpa.jpql.parser.ObjectExpression; import org.eclipse.persistence.jpa.jpql.parser.OnClause; import org.eclipse.persistence.jpa.jpql.parser.OrExpression; import org.eclipse.persistence.jpa.jpql.parser.OrderByClause; import org.eclipse.persistence.jpa.jpql.parser.OrderByItem; 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.SizeExpression; import org.eclipse.persistence.jpa.jpql.parser.SqrtExpression; import org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression; import org.eclipse.persistence.jpa.jpql.parser.StringLiteral; import org.eclipse.persistence.jpa.jpql.parser.SubExpression; import org.eclipse.persistence.jpa.jpql.parser.SubstringExpression; import org.eclipse.persistence.jpa.jpql.parser.SubtractionExpression; import org.eclipse.persistence.jpa.jpql.parser.SumFunction; import org.eclipse.persistence.jpa.jpql.parser.TreatExpression; import org.eclipse.persistence.jpa.jpql.parser.TrimExpression; import org.eclipse.persistence.jpa.jpql.parser.TypeExpression; import org.eclipse.persistence.jpa.jpql.parser.UnknownExpression; import org.eclipse.persistence.jpa.jpql.parser.UpdateClause; import org.eclipse.persistence.jpa.jpql.parser.UpdateItem; import org.eclipse.persistence.jpa.jpql.parser.UpdateStatement; import org.eclipse.persistence.jpa.jpql.parser.UpperExpression; import org.eclipse.persistence.jpa.jpql.parser.ValueExpression; import org.eclipse.persistence.jpa.jpql.parser.WhenClause; import org.eclipse.persistence.jpa.jpql.parser.WhereClause; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.*; /** * The base validator responsible to gather the problems found in a JPQL query by validating the * content to make sure it is semantically valid, i.e. based on the information contained in the * JPA application. The grammar is not validated by this visitor. * <p> * Provisional API: This interface is part of an interim API that is still under development and * expected to change significantly before reaching stability. It is available at this early stage * to solicit feedback from pioneering adopters on the understanding that any code that uses this * API will almost certainly be broken (repeatedly) as the API evolves. * * @see AbstractGrammarValidator * * @version 2.5.1 * @since 2.4 * @author Pascal Filion */ @SuppressWarnings("nls") public abstract class AbstractSemanticValidator extends AbstractValidator { /** * This visitor is responsible to retrieve the visited {@link Expression} if it is a * {@link CollectionValuedPathExpression}. */ protected CollectionValuedPathExpressionVisitor collectionValuedPathExpressionVisitor; /** * This visitor is responsible to check if the entity type literal (parsed as an identification * variable) is part of a comparison expression. */ private ComparingEntityTypeLiteralVisitor comparingEntityTypeLiteralVisitor; /** * This visitor visits the left and right expressions of a {@link ComparisonExpressionVisitor} * and gather information that is used by {@link #validateComparisonExpression(ComparisonExpression)}. */ private ComparisonExpressionVisitor comparisonExpressionVisitor; /** * The given helper allows this validator to access the JPA artifacts without using Hermes SPI. */ protected final SemanticValidatorHelper helper; /** * This visitor makes sure the path expressions are properly validated. */ private InItemsVisitor inItemsVisitor; /** * This flag is used to register the {@link IdentificationVariable IdentificationVariables} that * are used throughout the query (top-level query and subqueries), except the identification * variables defining an abstract schema name or a collection-valued path expression. */ protected boolean registerIdentificationVariable; /** * This visitor is responsible to retrieve the visited {@link Expression} if it is a * {@link StateFieldPathExpression}. */ protected StateFieldPathExpressionVisitor stateFieldPathExpressionVisitor; /** * */ private SubqueryFirstDeclarationVisitor subqueryFirstDeclarationVisitor; /** * */ private TopLevelFirstDeclarationVisitor topLevelFirstDeclarationVisitor; /** * The {@link IdentificationVariable IdentificationVariables} that are used throughout the query * (top-level query and subqueries), except the identification variables defining an abstract * schema name or a collection-valued path expression. */ protected List<IdentificationVariable> usedIdentificationVariables; /** * This finder is responsible to retrieve the virtual identification variable from the * <b>UPDATE</b> range declaration since it is optional. */ protected BaseDeclarationIdentificationVariableFinder virtualIdentificationVariableFinder; /** * Creates a new <code>AbstractSemanticValidator</code>. * * @param helper The given helper allows this validator to access the JPA artifacts without using * Hermes SPI * @exception NullPointerException The given {@link SemanticValidatorHelper} cannot be <code>null</code> */ protected AbstractSemanticValidator(SemanticValidatorHelper helper) { super(); Assert.isNotNull(helper, "The helper cannot be null"); this.helper = helper; } protected ComparingEntityTypeLiteralVisitor buildComparingEntityTypeLiteralVisitor() { return new ComparingEntityTypeLiteralVisitor(); } protected InItemsVisitor buildInItemsVisitor() { return new InItemsVisitor(this); } protected SubqueryFirstDeclarationVisitor buildSubqueryFirstDeclarationVisitor() { return new SubqueryFirstDeclarationVisitor(); } protected TopLevelFirstDeclarationVisitor buildTopLevelFirstDeclarationVisitor() { return new TopLevelFirstDeclarationVisitor(); } /** * {@inheritDoc} */ @Override public void dispose() { super.dispose(); usedIdentificationVariables.clear(); } /** * Returns the {@link IdentificationVariable} that defines the identification variable for either * a <code><b>DELETE</b></code> or an <code><b>UPDATE</b></code> query. * * @param expression The {@link AbstractSchemaName} that is being validated and that most likely * representing an associated path expression and not an entity name * @return The {@link IdentificationVariable} defining either the identification variable or the * virtual identification variable for the <code><b>DELETE</b></code> or for the * <code><b>UPDATE</b></code> query */ protected IdentificationVariable findVirtualIdentificationVariable(AbstractSchemaName expression) { BaseDeclarationIdentificationVariableFinder visitor = getVirtualIdentificationVariableFinder(); try { expression.accept(visitor); return visitor.expression; } finally { visitor.expression = null; } } protected CollectionValuedPathExpression getCollectionValuedPathExpression(Expression expression) { CollectionValuedPathExpressionVisitor visitor = getCollectionValuedPathExpressionVisitor(); try { expression.accept(visitor); return visitor.expression; } finally { visitor.expression = null; } } protected CollectionValuedPathExpressionVisitor getCollectionValuedPathExpressionVisitor() { if (collectionValuedPathExpressionVisitor == null) { collectionValuedPathExpressionVisitor = new CollectionValuedPathExpressionVisitor(); } return collectionValuedPathExpressionVisitor; } protected ComparingEntityTypeLiteralVisitor getComparingEntityTypeLiteralVisitor() { if (comparingEntityTypeLiteralVisitor == null) { comparingEntityTypeLiteralVisitor = buildComparingEntityTypeLiteralVisitor(); } return comparingEntityTypeLiteralVisitor; } protected ComparisonExpressionVisitor getComparisonExpressionVisitor() { if (comparisonExpressionVisitor == null) { comparisonExpressionVisitor = new ComparisonExpressionVisitor(this); } return comparisonExpressionVisitor; } /** * {@inheritDoc} */ @Override protected JPQLGrammar getGrammar() { return helper.getGrammar(); } protected InItemsVisitor getInItemsVisitor() { if (inItemsVisitor == null) { inItemsVisitor = buildInItemsVisitor(); } return inItemsVisitor; } protected StateFieldPathExpression getStateFieldPathExpression(Expression expression) { StateFieldPathExpressionVisitor visitor = getStateFieldPathExpressionVisitor(); try { expression.accept(visitor); return visitor.expression; } finally { visitor.expression = null; } } protected StateFieldPathExpressionVisitor getStateFieldPathExpressionVisitor() { if (stateFieldPathExpressionVisitor == null) { stateFieldPathExpressionVisitor = new StateFieldPathExpressionVisitor(); } return stateFieldPathExpressionVisitor; } /** * Returns the visitor that can find the {@link IdentificationVariable} of the {@link * RangeVariableDeclaration}. This should be used when the query is either a <code><b>DELETE</b></code> * or <code><b>UPDATE</b></code> query. * * @return The visitor that can traverse the query and returns the {@link IdentificationVariable} */ protected BaseDeclarationIdentificationVariableFinder getVirtualIdentificationVariableFinder() { if (virtualIdentificationVariableFinder == null) { virtualIdentificationVariableFinder = new BaseDeclarationIdentificationVariableFinder(); } return virtualIdentificationVariableFinder; } /** * {@inheritDoc} */ @Override protected void initialize() { super.initialize(); usedIdentificationVariables = new ArrayList<IdentificationVariable>(); registerIdentificationVariable = true; } /** * Determines whether the given identification variable is used in a comparison expression: * "expression = LargeProject". * * @param expression The {@link IdentificationVariable} used to determine its purpose * @return <code>true</code> if the identification variable is used in a comparison expression; * <code>false</code> otherwise */ protected boolean isComparingEntityTypeLiteral(IdentificationVariable expression) { ComparingEntityTypeLiteralVisitor visitor = getComparingEntityTypeLiteralVisitor(); try { visitor.expression = expression; expression.accept(visitor); return visitor.result; } finally { visitor.result = false; visitor.expression = null; } } protected boolean isIdentificationVariableDeclaredAfter(String variableName, int variableNameIndex, int joinIndex, List<JPQLQueryDeclaration> declarations) { for (int index = variableNameIndex, declarationCount = declarations.size(); index < declarationCount; index++) { JPQLQueryDeclaration declaration = declarations.get(index); // Ignore the current declaration since it's the same identification variable if (index != variableNameIndex) { // Check to make sure the Declaration is a known type if (declaration.getType() != Type.UNKNOWN) { String nextVariableName = declaration.getVariableName(); if (variableName.equalsIgnoreCase(nextVariableName)) { return true; } } // Some implementation could have a Declaration that is not a range, derived or a // collection Declaration, it could represent a JOIN expression else { return false; } } // Scan the JOIN expressions if (declaration.hasJoins()) { List<Join> joins = declaration.getJoins(); int endIndex = (index == variableNameIndex) ? joinIndex : joins.size(); for (int subIndex = joinIndex; subIndex < endIndex; subIndex++) { Join join = joins.get(subIndex); String joinVariableName = literal( join.getIdentificationVariable(), LiteralType.IDENTIFICATION_VARIABLE ); if (variableName.equalsIgnoreCase(joinVariableName)) { return true; } } } } // The identification variable was not found after the declaration being validated, that means // it was defined before or it was not defined (which is validated by another rule) return false; } /** * Determines whether an identification variable can be used in a comparison expression when the * operator is either {@literal '<', '<=', '>', '>='}. * * @param expression The {@link IdentificationVariable} that is mapped to either an entity, a * singled-object value field, a collection-valued object field * @return <code>true</code> if it can be used in a ordering comparison expression; <code>false</code> * if it can't */ protected boolean isIdentificationVariableValidInComparison(IdentificationVariable expression) { return helper.isIdentificationVariableValidInComparison(expression); } /** * Determines whether the given {@link ComparisonExpression} compares two expression using one of * the following operators: {@literal '<', '<=', '>', '>='}. * * @param expression The {@link ComparisonExpression} to check what type of operator that is used * @return <code>true</code> if the operator is used to check for order; <code>false</code> if it * is not */ protected boolean isOrderComparison(ComparisonExpression expression) { String operator = expression.getComparisonOperator(); return operator == Expression.GREATER_THAN || operator == Expression.GREATER_THAN_OR_EQUAL || operator == Expression.LOWER_THAN || operator == Expression.LOWER_THAN_OR_EQUAL; } /** * Determines whether the expression at the given index is valid or not. * * @param result The integer value containing the bit used to determine the state of an expression * at a given position * @param index The index that is used to determine the state of the expression * @return <code>true</code> if the expression is valid at the given index */ protected final boolean isValid(int result, int index) { return (result & (1 << index)) == 0; } /** * Returns the type of path expression that is allowed in the <code><b>SELECT</b></code> clause. * * @return The type of path expressions allowed. The spec defines it as basic or object mapping * only, i.e. collection-valued path expression is not allowed */ protected abstract PathType selectClausePathExpressionPathType(); protected FirstDeclarationVisitor subqueryFirstDeclarationVisitor() { if (subqueryFirstDeclarationVisitor == null) { subqueryFirstDeclarationVisitor = buildSubqueryFirstDeclarationVisitor(); } return subqueryFirstDeclarationVisitor; } protected FirstDeclarationVisitor topLevelFirstDeclarationVisitor() { if (topLevelFirstDeclarationVisitor == null) { topLevelFirstDeclarationVisitor = buildTopLevelFirstDeclarationVisitor(); } return topLevelFirstDeclarationVisitor; } /** * Updates the validation status of an expression at a specified position. The value is stored * in an integer value. * * @param result The integer value that is used to store the validation status of an expression * at the given position * @param index The position to store the validation status * @param valid The new validation status to store * @return The updated integer value */ protected final int updateStatus(int result, int index, boolean valid) { return valid ? (result & (0 << index)) : (result | (1 << index)); } /** * Validates the encapsulated expression of the given <code><b>ABS</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link AbsExpression} to validate by validating its encapsulated * expression * @return <code>false</code> if the encapsulated expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateAbsExpression(AbsExpression expression) { return validateFunctionPathExpression(expression); } /** * Validates the given <code><b>FROM</b></code> clause. This will validate the order of * identification variable declarations. * * @param expression The {@link AbstractFromClause} to validate * @param visitor */ protected void validateAbstractFromClause(AbstractFromClause expression, FirstDeclarationVisitor visitor) { // The identification variable declarations are evaluated from left to right in // the FROM clause, and an identification variable declaration can use the result // of a preceding identification variable declaration of the query string List<JPQLQueryDeclaration> declarations = helper.getDeclarations(); for (int index = 0, count = declarations.size(); index < count; index++) { JPQLQueryDeclaration declaration = declarations.get(index); // Make sure the first declaration is valid if (index == 0) { validateFirstDeclaration(expression, declaration, visitor); } else { Expression declarationExpression = declaration.getDeclarationExpression(); // A JOIN expression does not have a declaration and it should not be traversed here if (declarationExpression != null) { declarationExpression.accept(this); } } // Check the JOIN expressions in the identification variable declaration if (declaration.hasJoins()) { validateJoinsIdentificationVariable(expression, declarations, declaration, index); } // Check the collection member declaration or derived path expression else { Type type = declaration.getType(); if (type == Type.DERIVED || type == Type.COLLECTION) { // Retrieve the identification variable from the path expression String variableName = literal( declaration.getBaseExpression(), LiteralType.PATH_EXPRESSION_IDENTIFICATION_VARIABLE ); if (ExpressionTools.stringIsNotEmpty(variableName) && isIdentificationVariableDeclaredAfter(variableName, index, -1, declarations)) { int startPosition = position(declaration.getDeclarationExpression()) - variableName.length(); int endPosition = startPosition + variableName.length(); addProblem( expression, startPosition, endPosition, AbstractFromClause_WrongOrderOfIdentificationVariableDeclaration, variableName ); } } } } } /** * Validates the given {@link AbstractSchemaName}. The tests to perform are: * <ul> * <li>Check to see the actual entity associated with the entity name does exist.</li> * <li>If the abstract schema name is actually a path expression (which can be defined in a * subquery but is always parsed as an abstract schema name), then make sure the path * expression is resolving to a relationship mapping.</li> * </ul> * * @param expression The {@link AbstractSchemaName} to validate * @return <code>true</code> if the entity name was resolved; <code>false</code> otherwise */ protected boolean validateAbstractSchemaName(AbstractSchemaName expression) { String abstractSchemaName = expression.getText(); Object managedType = helper.getEntityNamed(abstractSchemaName); boolean valid = true; if (managedType == null) { // If a subquery is defined in a WHERE clause of an update query, // then check for a path expression if (isWithinSubquery(expression)) { // Find the identification variable from the UPDATE range declaration IdentificationVariable identificationVariable = findVirtualIdentificationVariable(expression); String variableName = (identificationVariable != null) ? identificationVariable.getText() : null; if (ExpressionTools.stringIsNotEmpty(variableName)) { Object mapping = helper.resolveMapping(variableName, abstractSchemaName); Object type = helper.getMappingType(mapping); // Does not resolve to a valid path if (!helper.isTypeResolvable(type)) { addProblem(expression, StateFieldPathExpression_NotResolvable, abstractSchemaName); valid = false; } // Not a relationship mapping else if (!helper.isRelationshipMapping(mapping)) { addProblem(expression, PathExpression_NotRelationshipMapping, abstractSchemaName); valid = false; } } else { addProblem(expression, AbstractSchemaName_Invalid, abstractSchemaName); valid = false; } } // The managed type does not exist else { addProblem(expression, AbstractSchemaName_Invalid, abstractSchemaName); valid = false; } } return valid; } /** * Validates the encapsulated expression of the given addition expression. The test to perform is: * <ul> * <li>If the left or right expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the left or right expression is not a path expression, validation will be redirected to * that expression but the returned status will not be updated.</li> * </ul> * * @param expression The {@link AdditionExpression} to validate by validating its encapsulated * expression * @return A number indicating the validation result. {@link #isValid(int, int)} can be used to * determine the validation status of an expression based on its position */ protected int validateAdditionExpression(AdditionExpression expression) { return validateArithmeticExpression( expression, AdditionExpression_LeftExpression_WrongType, AdditionExpression_RightExpression_WrongType ); } /** * Validates the given {@link AllOrAnyExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link AllOrAnyExpression} to validate */ protected void validateAllOrAnyExpression(AllOrAnyExpression expression) { super.visit(expression); } /** * Validates the given {@link AndExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link AndExpression} to validate */ protected void validateAndExpression(AndExpression expression) { super.visit(expression); } /** * Validates the type of the left and right expressions defined by the given {@link ArithmeticExpression}. * The test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link ArithmeticExpression} to validate * @param leftExpressionWrongTypeMessageKey The key used to describe the left expression does not * have a valid type * @param rightExpressionWrongTypeMessageKey The key used to describe the right expression does * not have a valid type * @return A number indicating the validation result. {@link #isValid(int, int)} can be used to * determine the validation status of an expression based on its position */ protected int validateArithmeticExpression(ArithmeticExpression expression, String leftExpressionWrongTypeMessageKey, String rightExpressionWrongTypeMessageKey) { return validateFunctionPathExpression(expression, PathType.BASIC_FIELD_ONLY); } /** * Validates the arithmetic factor expression. The test to perform is: * <ul> * <li>If the arithmetic factor is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * </ul> * * @param expression The {@link ArithmeticFactor} to validate * @return <code>false</code> if the arithmetic factor expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateArithmeticExpression(ArithmeticFactor expression) { boolean valid = true; if (expression.hasExpression()) { Expression factor = expression.getExpression(); // Special case for state field path expression, association field is not allowed StateFieldPathExpression pathExpression = getStateFieldPathExpression(factor); if (pathExpression != null) { valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY); } else { factor.accept(this); } } return valid; } /** * Validates the encapsulated expression of the given <code><b>AVG</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link AvgFunction} to validate by validating its encapsulated expression * @return <code>false</code> if the encapsulated expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateAvgFunction(AvgFunction expression) { return validateFunctionPathExpression(expression); } /** * Validates the given {@link BetweenExpression}. The test to perform is: * <ul> * <li>If the "first" expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * </ul> * * @param expression The {@link BetweenExpression} to validate */ protected int validateBetweenExpression(BetweenExpression expression) { int result = 0; // Validate the "first" expression if (expression.hasExpression()) { Expression firstExpression = expression.getExpression(); // Special case for state field path expression, association field is not allowed StateFieldPathExpression pathExpression = getStateFieldPathExpression(firstExpression); if (pathExpression != null) { boolean valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY); updateStatus(result, 0, valid); } else { firstExpression.accept(this); } } // Validate the lower bound expression expression.getLowerBoundExpression().accept(this); // Validate the upper bound expression expression.getUpperBoundExpression().accept(this); return result; } /** * Validates the given {@link CaseExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link CaseExpression} to validate */ protected void validateCaseExpression(CaseExpression expression) { super.visit(expression); } /** * Validates the given {@link CoalesceExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link CoalesceExpression} to validate */ protected void validateCoalesceExpression(CoalesceExpression expression) { super.visit(expression); } /** * Validates the given {@link CollectionMemberDeclaration}. * * @param expression The {@link CollectionMemberDeclaration} to validate */ protected void validateCollectionMemberDeclaration(CollectionMemberDeclaration expression) { validateCollectionValuedPathExpression(expression.getCollectionValuedPathExpression(), true); try { registerIdentificationVariable = false; expression.getIdentificationVariable().accept(this); } finally { registerIdentificationVariable = true; } } /** * Validates the given {@link CollectionMemberExpression}. Only the collection-valued path * expression is validated. * * @param expression The {@link CollectionMemberExpression} to validate * @return A number indicating the validation result. {@link #isValid(int, int)} can be used to * determine the validation status of an expression based on its position */ protected int validateCollectionMemberExpression(CollectionMemberExpression expression) { int result = 0; // Validate the entity expression if (expression.hasEntityExpression()) { Expression entityExpression = expression.getEntityExpression(); // Special case for state field path expression, association field is allowed StateFieldPathExpression pathExpression = getStateFieldPathExpression(entityExpression); if (pathExpression != null) { boolean valid = validateStateFieldPathExpression(pathExpression, PathType.ASSOCIATION_FIELD_ONLY); updateStatus(result, 0, valid); } else { entityExpression.accept(this); } } // Validate the collection-valued path expression boolean valid = validateCollectionValuedPathExpression(expression.getCollectionValuedPathExpression(), true); updateStatus(result, 1, valid); return result; } /** * Validates the given {@link Expression} and makes sure it's a valid collection value path expression. * * @param expression The {@link Expression} to validate * @param collectionTypeOnly <code>true</code> to make sure the path expression resolves to a * collection mapping only; <code>false</code> if it can simply resolves to a relationship mapping */ protected boolean validateCollectionValuedPathExpression(Expression expression, boolean collectionTypeOnly) { boolean valid = true; // The path expression resolves to a collection-valued path expression CollectionValuedPathExpression collectionValuedPathExpression = getCollectionValuedPathExpression(expression); if (collectionValuedPathExpression != null && collectionValuedPathExpression.hasIdentificationVariable() && !collectionValuedPathExpression.endsWithDot()) { // A collection_valued_field is designated by the name of an association field in a // one-to-many or a many-to-many relationship or by the name of an element collection field Object mapping = helper.resolveMapping(expression); Object type = helper.getMappingType(mapping); // Does not resolve to a valid path if (!helper.isTypeResolvable(type) || (mapping == null)) { int startPosition = position(expression); int endPosition = startPosition + length(expression); addProblem( expression, startPosition, endPosition, CollectionValuedPathExpression_NotResolvable, expression.toParsedText() ); valid = false; } else if (collectionTypeOnly && !helper.isCollectionMapping(mapping) || !collectionTypeOnly && !helper.isRelationshipMapping(mapping)) { int startPosition = position(expression); int endPosition = startPosition + length(expression); addProblem( expression, startPosition, endPosition, CollectionValuedPathExpression_NotCollectionType, expression.toParsedText() ); valid = false; } } return valid; } /** * Validates the left and right expressions of the given {@link ComparisonExpression}. The tests * to perform are: * <ul> * <li>If the comparison operator is either '=' or {@literal '<>'}. The expressions can only be * <ul> * <li>Two identification variables;</li> * <li>Two path expressions resolving to an association field;</li> * <li>One can be a path expression resolving to a basic field and the other one has to * resolve to a basic value.</li> * </ul> * </li> * <li>If the comparison operator is either {@literal '<', '<=', '>=', '>'}. The expressions cannot be * <ul> * <li>Two identification variables;</li> * <li>Two path expressions resolving to an association field;</li> * </ul> * </li> * </ul> * * @param expression The {@link ConcatExpression} to validate by validating its * left and right expressions * @return The status of the comparison between the left and right expression: <code>true</code> * if the two expressions pass the rules defined by this method; <code>false</code> otherwise */ protected boolean validateComparisonExpression(ComparisonExpression expression) { Expression leftExpression = expression.getLeftExpression(); Expression rightExpression = expression.getRightExpression(); boolean valid = true; // First determine what is being compared and validate them as well ComparisonExpressionVisitor validator = getComparisonExpressionVisitor(); try { // Visit the left expression and gather its information validator.validatingLeftExpression = true; leftExpression.accept(validator); // Visit the right expression and gather its information validator.validatingLeftExpression = false; rightExpression.accept(validator); // '<', '<=', '>=', '>' if (isOrderComparison(expression)) { // The left expression cannot be an identification variable if (validator.leftIdentificationVariable && validator.leftIdentificationVariableValid) { IdentificationVariable variable = (IdentificationVariable) leftExpression; // There is a specific EclipseLink case where it is valid to use an // identification variable, which is when the identification variable // maps to a direct collection mapping if (!isIdentificationVariableValidInComparison(variable)) { addProblem( leftExpression, ComparisonExpression_IdentificationVariable, leftExpression.toActualText(), expression.getComparisonOperator() ); valid = false; } } // The left expression is a path expression else if (validator.leftStateFieldPathExpression && validator.leftStateFieldPathExpressionValid) { Object mapping = helper.resolveMapping(leftExpression); // The path expression cannot be a non-basic mapping if ((mapping != null) && !helper.isPropertyMapping(mapping)) { addProblem( leftExpression, ComparisonExpression_AssociationField, leftExpression.toActualText(), expression.getComparisonOperator() ); valid = false; } } // The right expression cannot be an identification variable if (validator.rightIdentificationVariable && validator.rightIdentificationVariableValid) { IdentificationVariable variable = (IdentificationVariable) rightExpression; // There is a specific EclipseLink case where it is valid to use an // identification variable, which is when the identification variable // maps to a direct collection mapping if (!isIdentificationVariableValidInComparison(variable)) { addProblem( rightExpression, ComparisonExpression_IdentificationVariable, rightExpression.toActualText(), expression.getComparisonOperator() ); valid = false; } } // The right expression is a path expression else if (validator.rightStateFieldPathExpression && validator.rightStateFieldPathExpressionValid) { Object mapping = helper.resolveMapping(rightExpression); // The path expression cannot be a non-basic mapping if ((mapping != null) && !helper.isPropertyMapping(mapping)) { addProblem( rightExpression, ComparisonExpression_AssociationField, rightExpression.toActualText(), expression.getComparisonOperator() ); valid = false; } } } // '=', '<>' else { // The left expression is an identification variable // The right expression is a path expression if (validator.leftIdentificationVariable && validator.leftIdentificationVariableValid && validator.rightStateFieldPathExpression && validator.rightStateFieldPathExpressionValid) { Object mapping = helper.resolveMapping(rightExpression); IdentificationVariable variable = (IdentificationVariable) leftExpression; // The path expression can only be a non-basic mapping. // There is a specific EclipseLink case where it is valid to use an // identification variable, which is when the identification variable // maps to a direct collection mapping if ((mapping != null) && helper.isPropertyMapping(mapping) && !isIdentificationVariableValidInComparison(variable)) { addProblem( rightExpression, ComparisonExpression_BasicField, rightExpression.toActualText(), expression.getComparisonOperator() ); valid = false; } } // The left expression is a path expression // The right expression is an identification variable else if (validator.rightIdentificationVariable && validator.rightIdentificationVariableValid && validator.leftStateFieldPathExpression && validator.leftStateFieldPathExpressionValid) { Object mapping = helper.resolveMapping(leftExpression); IdentificationVariable variable = (IdentificationVariable) rightExpression; // The path expression can only be a non-basic mapping. // There is a specific EclipseLink case where it is valid to use an // identification variable, which is when the identification variable // maps to a direct collection mapping if ((mapping != null) && helper.isPropertyMapping(mapping) && !isIdentificationVariableValidInComparison(variable)) { addProblem( leftExpression, ComparisonExpression_BasicField, leftExpression.toActualText(), expression.getComparisonOperator() ); valid = false; } } } return valid; } finally { validator.dispose(); } } /** * Validates the encapsulated expression of the given <code><b>CONCAT</b></code> expression. * The tests to perform are: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a * basic mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected * to that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link ConcatExpression} to validate by validating its encapsulated expression * @return <code>false</code> if the first encapsulated expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateConcatExpression(ConcatExpression expression) { return validateFunctionPathExpression(expression); } /** * Validates the given {@link ConstructorExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link ConstructorExpression} to validate */ protected void validateConstructorExpression(ConstructorExpression expression) { super.visit(expression); } /** * Validates the given {@link CountFunction}. The default behavior does not require to * semantically validate it. * * @param expression The {@link CountFunction} to validate */ protected void validateCountFunction(CountFunction expression) { if (expression.hasExpression()) { Expression leftExpression = expression.getExpression(); StateFieldPathExpression pathExpression = getStateFieldPathExpression(leftExpression); if (pathExpression != null) { validateStateFieldPathExpression(pathExpression, validPathExpressionTypeForCountFunction()); } else { leftExpression.accept(this); } } } /** * Validates the given {@link DateTime}. The default behavior does not require to * semantically validate it. * * @param expression The {@link DateTime} to validate */ protected void validateDateTime(DateTime expression) { super.visit(expression); } /** * Validates the given {@link DeleteClause}. The default behavior does not require to * semantically validate it. * * @param expression The {@link DeleteClause} to validate */ protected void validateDeleteClause(DeleteClause expression) { super.visit(expression); } /** * Validates the given {@link DeleteStatement}. The default behavior does not require to * semantically validate it. * * @param expression The {@link DeleteStatement} to validate */ protected void validateDeleteStatement(DeleteStatement expression) { super.visit(expression); } /** * Validates the encapsulated expression of the given division expression. The test to perform is: * <ul> * <li>If the left or right expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the left or right expression is not a path expression, validation will be redirected to * that expression but the returned status will not be updated.</li> * </ul> * * @param expression The {@link DivisionExpression} to validate by validating its encapsulated * expression * @return A number indicating the validation result. {@link #isValid(int, int)} can be used to * determine the validation status of an expression based on its position */ protected int validateDivisionExpression(DivisionExpression expression) { return validateArithmeticExpression( expression, DivisionExpression_LeftExpression_WrongType, DivisionExpression_RightExpression_WrongType ); } protected boolean validateEntityTypeLiteral(EntityTypeLiteral expression) { String entityTypeName = expression.getEntityTypeName(); boolean valid = true; if (ExpressionTools.stringIsNotEmpty(entityTypeName)) { Object entity = helper.getEntityNamed(entityTypeName); if (entity == null) { int startIndex = position(expression); int endIndex = startIndex + entityTypeName.length(); addProblem(expression, startIndex, endIndex, EntityTypeLiteral_NotResolvable, entityTypeName); valid = false; } } return valid; } /** * Validates the given {@link EntryExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link EntryExpression} to validate */ protected void validateEntryExpression(EntryExpression expression) { super.visit(expression); } /** * Validates the given {@link ExistsExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link ExistsExpression} to validate */ protected void validateExistsExpression(ExistsExpression expression) { super.visit(expression); } protected void validateFirstDeclaration(AbstractFromClause expression, JPQLQueryDeclaration declaration, FirstDeclarationVisitor visitor) { Expression declarationExpression = declaration.getDeclarationExpression(); Expression baseExpression = declaration.getBaseExpression(); try { baseExpression.accept(visitor); if (!visitor.valid) { int startPosition = position(declarationExpression); int endPosition = startPosition + length(declarationExpression); addProblem( expression, startPosition, endPosition, AbstractFromClause_InvalidFirstIdentificationVariableDeclaration, baseExpression.toActualText() ); } else { declarationExpression.accept(this); } } finally { visitor.valid = false; } } /** * Validates the given {@link FromClause}. * * @param expression The {@link FromClause} to validate */ protected void validateFromClause(FromClause expression) { validateAbstractFromClause(expression, topLevelFirstDeclarationVisitor()); } /** * Validates the given {@link FunctionExpression}. * * @param expression The {@link FunctionExpression} to validate */ protected void validateFunctionExpression(FunctionExpression expression) { } /** * Validates the given {@link AbstractSingleEncapsulatedExpression}'s encapsulated expression if * it is a state field path expression and makes sure it is mapping to a basic mapping. That * means relationship field mapping is not allowed. * * @param expression The {@link AbstractSingleEncapsulatedExpression} to validate its encapsulated * expression if it's a state field path expression, otherwise does nothing * @return <code>false</code> if the encapsulated expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateFunctionPathExpression(AbstractSingleEncapsulatedExpression expression) { boolean valid = true; if (expression.hasEncapsulatedExpression()) { Expression encapsulatedExpression = expression.getExpression(); // Special case for state field path expression, association field is not allowed StateFieldPathExpression pathExpression = getStateFieldPathExpression(encapsulatedExpression); if (pathExpression != null) { valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY); } else { encapsulatedExpression.accept(this); } } return valid; } /** * Validates the left and right expressions of the given compound expression. The test to perform is: * <ul> * <li>If the left or the right expression is a path expression, validation makes sure it is a * basic mapping, an association field is not allowed.</li> * </ul> * * @param expression The {@link CompoundExpression} to validate by validating its left and right * expressions * @param pathType The type of field that is allowed * @return A number indicating the validation result. {@link #isValid(int, int)} can be used to * determine the validation status of an expression based on its position */ protected int validateFunctionPathExpression(CompoundExpression expression, PathType pathType) { int result = 0; // Left expression if (expression.hasLeftExpression()) { Expression leftExpression = expression.getLeftExpression(); StateFieldPathExpression pathExpression = getStateFieldPathExpression(leftExpression); if (pathExpression != null) { boolean valid = validateStateFieldPathExpression(pathExpression, pathType); updateStatus(result, 0, valid); } else { leftExpression.accept(this); } } // Right expression if (expression.hasRightExpression()) { Expression rightExpression = expression.getRightExpression(); StateFieldPathExpression pathExpression = getStateFieldPathExpression(rightExpression); if (pathExpression != null) { boolean valid = validateStateFieldPathExpression(pathExpression, pathType); updateStatus(result, 1, valid); } else { rightExpression.accept(this); } } return result; } /** * Validates the given {@link GroupByClause}. The default behavior does not require to * semantically validate it. * * @param expression The {@link GroupByClause} to validate */ protected void validateGroupByClause(GroupByClause expression) { super.visit(expression); } /** * Validates the given {@link HavingClause}. The default behavior does not require to * semantically validate it. * * @param expression The {@link HavingClause} to validate */ protected void validateHavingClause(HavingClause expression) { super.visit(expression); } /** * Validates the given {@link IdentificationVariable}. The test to perform are: * <ul> * <li>If the identification variable resolves to an entity type literal, then no validation * is performed.</li> * <li></li> * </ul> * * @param expression The identification variable to be validated * @return <code>true</code> if the given identification variable is valid; <code>false</code> * otherwise */ protected boolean validateIdentificationVariable(IdentificationVariable expression) { boolean valid = true; // Only a non-virtual identification variable is validated if (!expression.isVirtual()) { String variable = expression.getText(); boolean continueValidating = true; // A entity literal type is parsed as an identification variable, check for that case if (isComparingEntityTypeLiteral(expression)) { // The identification variable (or entity type literal) does not // correspond to an entity name, then continue validation Object entity = helper.getEntityNamed(variable); continueValidating = (entity == null); } // Validate a real identification variable if (continueValidating) { if (registerIdentificationVariable) { usedIdentificationVariables.add(expression); } valid = validateIdentificationVariable(expression, variable); } } // The identification variable actually represents a state field path expression that has // a virtual identification, validate that state field path expression instead else { StateFieldPathExpression pathExpression = expression.getStateFieldPathExpression(); if (pathExpression != null) { pathExpression.accept(this); } } return valid; } /** * Validates the given identification variable. The default behavior is to not validate it. * * @param expression The {@link IdentificationVariable} that is being visited * @param variable The actual identification variable, which is never an empty string * @return <code>true</code> if the given identification variable is valid; <code>false</code> * otherwise */ protected boolean validateIdentificationVariable(IdentificationVariable expression, String variable) { return true; } /** * Validates the given {@link InExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link InExpression} to validate */ protected void validateIdentificationVariableDeclaration(IdentificationVariableDeclaration expression) { super.visit(expression); } /** * Validates the identification variables: * <ul> * <li>Assures those used throughout the query have been defined in the <code><b>FROM</b></code> * clause in the current subquery or in a superquery.</li> * <li>They have been defined only once.</li> * </ul> */ protected void validateIdentificationVariables() { // Collect the identification variables from the Declarations Map<String, List<IdentificationVariable>> identificationVariables = new HashMap<String, List<IdentificationVariable>>(); helper.collectLocalDeclarationIdentificationVariables(identificationVariables); // Check for duplicate identification variables for (Map.Entry<String, List<IdentificationVariable>> entry : identificationVariables.entrySet()) { List<IdentificationVariable> variables = entry.getValue(); // More than one identification variable was used in a declaration if (variables.size() > 1) { for (IdentificationVariable variable : variables) { addProblem(variable, IdentificationVariable_Invalid_Duplicate, variable.getText()); } } } // Now collect the identification variables from the parent queries identificationVariables.clear(); helper.collectAllDeclarationIdentificationVariables(identificationVariables); // Check for undeclared identification variables for (IdentificationVariable identificationVariable : usedIdentificationVariables) { String variableName = identificationVariable.getText(); if (ExpressionTools.stringIsNotEmpty(variableName) && !identificationVariables.containsKey(variableName.toUpperCase())) { addProblem(identificationVariable, IdentificationVariable_Invalid_NotDeclared, variableName); } } } /** * Validates the given {@link IndexExpression}. It validates the identification variable and * makes sure is it defined in <code><b>IN</b></code> or <code><b>IN</b></code> expression. * * @param expression The {@link IndexExpression} to validate */ protected boolean validateIndexExpression(IndexExpression expression) { boolean valid = true; // The INDEX function can only be applied to identification variables denoting types for // which an order column has been specified String variableName = literal( expression.getExpression(), LiteralType.IDENTIFICATION_VARIABLE ); // The identification variable is not defined in a JOIN or IN expression if (ExpressionTools.stringIsNotEmpty(variableName) && !helper.isCollectionIdentificationVariable(variableName)) { addProblem(expression.getExpression(), IndexExpression_WrongVariable, variableName); valid = false; } return valid; } /** * Validates the given {@link InExpression}. The test to perform is: * <ul> * <li>If the expression is a path expression, validation makes sure it is an association mapping, * a basic field is not allowed.</li> * </ul> * * @param expression The {@link InExpression} to validate */ protected void validateInExpression(InExpression expression) { // Validate the left expression if (expression.hasExpression()) { Expression stringExpression = expression.getExpression(); // Special case for state field path expression StateFieldPathExpression pathExpression = getStateFieldPathExpression(stringExpression); if (pathExpression != null) { validateStateFieldPathExpression(pathExpression, validPathExpressionTypeForInExpression()); } else { stringExpression.accept(this); } } // Validate the items expression.getInItems().accept(getInItemsVisitor()); } /** * Validates the given {@link Join}. * * @param expression The {@link ValueExpression} to validate */ protected void validateJoin(Join expression) { if (expression.hasJoinAssociationPath()) { Expression joinAssociationPath = expression.getJoinAssociationPath(); validateCollectionValuedPathExpression(joinAssociationPath, false); joinAssociationPath.accept(this); } if (expression.hasIdentificationVariable()) { try { registerIdentificationVariable = false; expression.getIdentificationVariable().accept(this); } finally { registerIdentificationVariable = true; } } } protected void validateJoinsIdentificationVariable(AbstractFromClause expression, List<JPQLQueryDeclaration> declarations, JPQLQueryDeclaration declaration, int index) { List<Join> joins = declaration.getJoins(); for (int joinIndex = 0, joinCount = joins.size(); joinIndex < joinCount; joinIndex++) { Join join = joins.get(joinIndex); // Retrieve the identification variable from the join association path String variableName = literal( join.getJoinAssociationPath(), LiteralType.PATH_EXPRESSION_IDENTIFICATION_VARIABLE ); // Make sure the identification variable is defined before the JOIN expression if (ExpressionTools.stringIsNotEmpty(variableName) && isIdentificationVariableDeclaredAfter(variableName, index, joinIndex, declarations)) { int startPosition = position(join.getJoinAssociationPath()); int endPosition = startPosition + variableName.length(); addProblem( expression, startPosition, endPosition, AbstractFromClause_WrongOrderOfIdentificationVariableDeclaration, variableName ); } } } /** * Validates the given {@link KeyExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link KeyExpression} to validate */ protected void validateKeyExpression(KeyExpression expression) { super.visit(expression); } /** * Validates the encapsulated expression of the given <code><b>LENGTH</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link LengthExpression} to validate by validating its encapsulated expression * @return <code>false</code> if the encapsulated expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateLengthExpression(LengthExpression expression) { return validateFunctionPathExpression(expression); } /** * Validates the string expression of the given <code><b>LIKE</b></code> expression. The test to * perform is: * <ul> * <li>If the string expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link LengthExpression} to validate by validating its string expression * @return A number indicating the validation result. {@link #isValid(int, int)} can be used to * determine the validation status of an expression based on its position */ protected int validateLikeExpression(LikeExpression expression) { int result = 0; // Validate the "first" expression if (expression.hasStringExpression()) { Expression stringExpression = expression.getStringExpression(); // Special case for state field path expression, association field is not allowed StateFieldPathExpression pathExpression = getStateFieldPathExpression(stringExpression); if (pathExpression != null) { boolean valid = validateStateFieldPathExpression(pathExpression, validPathExpressionTypeForStringExpression()); updateStatus(result, 0, valid); } else { stringExpression.accept(this); } } // Validate the pattern value expression.getPatternValue().accept(this); // Validate the escape character expression.getEscapeCharacter().accept(this); return result; } /** * Validates the encapsulated expression of the given <code><b>LOCATE</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link LocateExpression} to validate by validating its encapsulated expression * @return A number indicating the validation result. {@link #isValid(int, int)} can be used to * determine the validation status of an expression based on its position */ protected int validateLocateExpression(LocateExpression expression) { int result = 0; // Validate the first expression if (expression.hasFirstExpression()) { Expression firstExpression = expression.getFirstExpression(); // Special case for state field path expression, association field is not allowed StateFieldPathExpression pathExpression = getStateFieldPathExpression(firstExpression); if (pathExpression != null) { boolean valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY); updateStatus(result, 0, valid); } else { firstExpression.accept(this); } } // Validate the second expression expression.getSecondExpression().accept(this); // Validate the third expression expression.getThirdExpression().accept(this); return result; } /** * Validates the encapsulated expression of the given <code><b>LOWER</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link LowerExpression} to validate by validating its encapsulated expression * @return <code>false</code> if the encapsulated expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateLowerExpression(LowerExpression expression) { return validateFunctionPathExpression(expression); } /** * Validates the encapsulated expression of the given <code><b>MAX</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link MaxFunction} to validate by validating its encapsulated expression * @return <code>false</code> if the encapsulated expression was validated and is not valid; * <code>true</code> otherwise */ protected boolean validateMaxFunction(MaxFunction expression) { // Arguments to the functions MAX must correspond to orderable state field types (i.e., // numeric types, string types, character types, or date types) return validateFunctionPathExpression(expression); } /** * Validates the encapsulated expression of the given <code><b>MIN</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link MinFunction} to validate by validating its encapsulated expression * @return <code>false</code> if the encapsulated expression was validated and is not valid; * <code>true</code> otherwise */ protected boolean validateMinFunction(MinFunction expression) { // Arguments to the functions MIN must correspond to orderable state field types (i.e., // numeric types, string types, character types, or date types) return validateFunctionPathExpression(expression); } /** * Validates the encapsulated expression of the given <code><b>MOD</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link ModExpression} to validate by validating its encapsulated expression * @return A number indicating the validation result. {@link #isValid(int, int)} can be used to * determine the validation status of an expression based on its position */ protected int validateModExpression(ModExpression expression) { int result = 0; // Validate the first expression if (expression.hasFirstExpression()) { Expression firstExpression = expression.getFirstExpression(); // Special case for state field path expression, association field is not allowed StateFieldPathExpression pathExpression = getStateFieldPathExpression(firstExpression); if (pathExpression != null) { boolean valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY); updateStatus(result, 0, valid); } else { firstExpression.accept(this); } } // Validate the second expression expression.getSecondExpression().accept(this); return result; } /** * Validates the encapsulated expression of the given multiplication expression. The test to * perform is: * <ul> * <li>If the left or right expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the left or right expression is not a path expression, validation will be redirected to * that expression but the returned status will not be updated.</li> * </ul> * * @param expression The {@link MultiplicationExpression} to validate by validating its encapsulated * expression * @return A number indicating the validation result. {@link #isValid(int, int)} can be used to * determine the validation status of an expression based on its position */ protected int validateMultiplicationExpression(MultiplicationExpression expression) { return validateArithmeticExpression( expression, MultiplicationExpression_LeftExpression_WrongType, MultiplicationExpression_RightExpression_WrongType ); } /** * Validates the given {@link NotExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link NotExpression} to validate */ protected void validateNotExpression(NotExpression expression) { super.visit(expression); } /** * Validates the given {@link NullComparisonExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link NullComparisonExpression} to validate */ protected void validateNullComparisonExpression(NullComparisonExpression expression) { super.visit(expression); } /** * Validates the given {@link NullIfExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link NullIfExpression} to validate */ protected void validateNullIfExpression(NullIfExpression expression) { super.visit(expression); } /** * Validates the given {@link ObjectExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link ObjectExpression} to validate */ protected void validateObjectExpression(ObjectExpression expression) { super.visit(expression); } /** * Validates the given {@link OnClause}. The default behavior does not require to semantically * validate it. * * @param expression The {@link OnClause} to validate */ protected void validateOnClause(OnClause expression) { super.visit(expression); } /** * Validates the given {@link OrderByItem}. The default behavior does not require to * semantically validate it. * * @param expression The {@link OrderByItem} to validate */ protected void validateOrderByClause(OrderByClause expression) { super.visit(expression); } /** * Validates the given {@link OrderByItem}. The default behavior does not require to * semantically validate it. * * @param expression The {@link OrderByItem} to validate */ protected void validateOrderByItem(OrderByItem expression) { super.visit(expression); } /** * Validates the given {@link OrExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link OrExpression} to validate */ protected void validateOrExpression(OrExpression expression) { super.visit(expression); } /** * Validates the given {@link RangeVariableDeclaration}. * * @param expression The {@link RangeVariableDeclaration} to validate */ protected void validateRangeVariableDeclaration(RangeVariableDeclaration expression) { validateRangeVariableDeclarationRootObject(expression); try { registerIdentificationVariable = false; expression.getIdentificationVariable().accept(this); } finally { registerIdentificationVariable = true; } } /** * Validates the "root" object of the given {@link RangeVariableDeclaration}. * * @param expression The {@link RangeVariableDeclaration} that needs its "root" object * to be validated */ protected void validateRangeVariableDeclarationRootObject(RangeVariableDeclaration expression) { expression.getRootObject().accept(this); } /** * Validates the given {@link ResultVariable}. The default behavior does not require to * semantically validate it. * * @param expression The {@link ResultVariable} to validate */ protected void validateResultVariable(ResultVariable expression) { // TODO: Validate identification to make sure it does not // collide with one defined in the FROM clause super.visit(expression); } /** * Validates the given {@link SelectClause}. The default behavior does not require to * semantically validate it. * * @param expression The {@link SelectClause} to validate */ protected void validateSelectClause(SelectClause expression) { Expression selectExpression = expression.getSelectExpression(); // Special case for state field path expression, all types are allowed StateFieldPathExpression pathExpression = getStateFieldPathExpression(selectExpression); if (pathExpression != null) { validateStateFieldPathExpression(pathExpression, selectClausePathExpressionPathType()); } else { selectExpression.accept(this); } } /** * Validates the given {@link SelectStatement}. The default behavior does not require to * semantically validate it. * * @param expression The {@link SelectStatement} to validate */ protected void validateSelectStatement(SelectStatement expression) { super.visit(expression); } /** * Validates the given {@link SimpleFromClause}. * * @param expression The {@link SimpleFromClause} to validate */ protected void validateSimpleFromClause(SimpleFromClause expression) { validateAbstractFromClause(expression, subqueryFirstDeclarationVisitor()); } /** * Validates the given {@link SimpleSelectClause}. The default behavior does not require to * semantically validate it. * * @param expression The {@link SimpleSelectClause} to validate */ protected void validateSimpleSelectClause(SimpleSelectClause expression) { super.visit(expression); } protected void validateSimpleSelectStatement(SimpleSelectStatement expression) { // Keep a copy of the identification variables that are used throughout the parent query List<IdentificationVariable> oldUsedIdentificationVariables = new ArrayList<IdentificationVariable>(usedIdentificationVariables); // Create a context for the subquery helper.newSubqueryContext(expression); try { super.visit(expression); // Validate the identification variables that are used within the subquery validateIdentificationVariables(); } finally { // Revert back to the parent context helper.disposeSubqueryContext(); // Revert the list to what it was usedIdentificationVariables.retainAll(oldUsedIdentificationVariables); } } /** * Validates the given {@link SizeExpression}. * * @param expression The {@link SizeExpression} to validate * @return <code>false</code> if the encapsulated expression is a collection-valued path expression * and it was found to be invalid; <code>true</code> otherwise */ protected boolean validateSizeExpression(SizeExpression expression) { // The SIZE function returns an integer value, the number of elements of the collection return validateCollectionValuedPathExpression(expression.getExpression(), true); } /** * Validates the encapsulated expression of the given <code><b>SQRT</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link SqrtExpression} to validate by validating its encapsulated expression * @return <code>false</code> if the encapsulated expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateSqrtExpression(SqrtExpression expression) { return validateFunctionPathExpression(expression); } /** * Validates the given {@link StateFieldPathExpression}. * * @param expression The {@link StateFieldPathExpression} the validate * @param pathType The type of field that is allowed * @return <code>true</code> if the given {@link StateFieldPathExpression} resolves to a valid * path; <code>false</code> otherwise */ protected boolean validateStateFieldPathExpression(StateFieldPathExpression expression, PathType pathType) { boolean valid = true; if (expression.hasIdentificationVariable()) { expression.getIdentificationVariable().accept(this); // A single_valued_object_field is designated by the name of an association field in a // one-to-one or many-to-one relationship or a field of embeddable class type if (!expression.endsWithDot()) { Object mapping = helper.resolveMapping(expression); // Validate the mapping if (mapping != null) { // It is syntactically illegal to compose a path expression from a path expression // that evaluates to a collection if (helper.isCollectionMapping(mapping)) { if (pathType != PathType.ANY_FIELD_INCLUDING_COLLECTION) { addProblem(expression, StateFieldPathExpression_CollectionType, expression.toActualText()); valid = false; } } // A transient mapping is not allowed else if (helper.isTransient(mapping)) { addProblem(expression, StateFieldPathExpression_NoMapping, expression.toParsedText()); valid = false; } // Only a basic field is allowed else if ((pathType == PathType.BASIC_FIELD_ONLY) && !helper.isPropertyMapping(mapping)) { addProblem(expression, StateFieldPathExpression_AssociationField, expression.toActualText()); valid = false; } // Only an association field is allowed else if ((pathType == PathType.ASSOCIATION_FIELD_ONLY) && helper.isPropertyMapping(mapping)) { addProblem(expression, StateFieldPathExpression_BasicField, expression.toActualText()); valid = false; } } // Attempts to resolve something that does not represent a mapping else { Object type = helper.getType(expression.toParsedText(0, expression.pathSize() - 1)); // An enum constant could have been parsed as a state field path expression if (helper.isEnumType(type)) { // Search for the enum constant String enumConstant = expression.getPath(expression.pathSize() - 1); boolean found = false; for (String constant : helper.getEnumConstants(type)) { if (constant.equals(enumConstant)) { found = true; break; } } if (!found) { int startIndex = position(expression) + helper.getTypeName(type).length() + 1; int endIndex = startIndex + enumConstant.length(); addProblem(expression, startIndex, endIndex, StateFieldPathExpression_InvalidEnumConstant, enumConstant); valid = false; } // Remove the used identification variable since it is the first // package name of the fully qualified enum constant usedIdentificationVariables.remove(expression.getIdentificationVariable()); } else { // Special case for EclipseLink, path expression formed with the identification variable // mapped to a subquery or database table cannot be resolved, thus cannot be validated Boolean status = validateThirdPartyStateFieldPathExpression(expression); // Third path extension validated the path expression if (status != null) { valid = status; } // Third path extension did not validate the path expression, continue validating it else { type = helper.getType(expression); // Does not resolve to a valid path if (!helper.isTypeResolvable(type)) { addProblem(expression, StateFieldPathExpression_NotResolvable, expression.toActualText()); valid = false; } // No mapping can be found for that path else { addProblem(expression, StateFieldPathExpression_NoMapping, expression.toActualText()); valid = false; } } } } } } return valid; } /** * Validates the encapsulated expression of the given <code><b>SUBSTRING</b></code> expression. * The test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link SubstringExpression} to validate by validating its encapsulated expression * @return A number indicating the validation result. {@link #isValid(int, int)} can be used to * determine the validation status of an expression based on its position */ protected int validateSubstringExpression(SubstringExpression expression) { int result = 0; // Validate the first expression if (expression.hasFirstExpression()) { Expression firstExpression = expression.getFirstExpression(); // Special case for state field path expression, association field is not allowed StateFieldPathExpression pathExpression = getStateFieldPathExpression(firstExpression); if (pathExpression != null) { boolean valid = validateStateFieldPathExpression(pathExpression, PathType.BASIC_FIELD_ONLY); updateStatus(result, 0, valid); } else { firstExpression.accept(this); } } // Validate the second expression expression.getSecondExpression().accept(this); // Validate the third expression expression.getThirdExpression().accept(this); return result; } /** * Validates the encapsulated expression of the given subtraction expression. The test to perform is: * <ul> * <li>If the left or right expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the left or right expression is not a path expression, validation will be redirected to * that expression but the returned status will not be updated.</li> * </ul> * * @param expression The {@link SubtractionExpression} to validate by validating its encapsulated * expression * @return A number indicating the validation result. {@link #isValid(int, int)} can be used to * determine the validation status of an expression based on its position */ protected int validateSubtractionExpression(SubtractionExpression expression) { return validateArithmeticExpression( expression, SubtractionExpression_LeftExpression_WrongType, SubtractionExpression_RightExpression_WrongType ); } /** * Validates the encapsulated expression of the given <code><b>MOD</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link ModExpression} to validate by validating its encapsulated expression * @return <code>false</code> if the encapsulated expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateSumFunction(SumFunction expression) { return validateFunctionPathExpression(expression); } /** * Validates the given {@link StateFieldPathExpression}, which means the path does not represent * a mapping, or an enum constant. * * @param expression The {@link StateFieldPathExpression} the validate * @return <code>Boolean.TRUE</code> or <code>Boolean.FALSE</code> if the given {@link * StateFieldPathExpression} was validated by this method; <code>null</code> if it could not be * validated (as being valid or invalid) */ protected Boolean validateThirdPartyStateFieldPathExpression(StateFieldPathExpression expression) { return null; } /** * Validates the given {@link TreatExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link TreatExpression} to validate */ protected void validateTreatExpression(TreatExpression expression) { super.visit(expression); } /** * Validates the encapsulated expression of the given <code><b>TRIM</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link TrimExpression} to validate by validating its encapsulated expression * @return <code>false</code> if the encapsulated expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateTrimExpression(TrimExpression expression) { return validateFunctionPathExpression(expression); } /** * Validates the given {@link TypeExpression}. The test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is an * association field, a basic field is not allowed.</li> * </ul> * * @param expression The {@link TypeExpression} to validate * @return <code>false</code> if the encapsulated expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateTypeExpression(TypeExpression expression) { // Validate the expression if (expression.hasEncapsulatedExpression()) { Expression encapsulatedExpression = expression.getExpression(); // Special case for state field path expression, only association field is allowed StateFieldPathExpression pathExpression = getStateFieldPathExpression(encapsulatedExpression); if (pathExpression != null) { return validateStateFieldPathExpression(pathExpression, PathType.ASSOCIATION_FIELD_ONLY); } else { encapsulatedExpression.accept(this); } } return true; } /** * Validates the given {@link UpdateClause}. The default behavior does not require to * semantically validate it. * * @param expression The {@link UpdateClause} to validate */ protected void validateUpdateClause(UpdateClause expression) { super.visit(expression); } /** * Validates the given {@link UpdateItem} by validating the traversability of the path * expression. The path expression is valid if it follows one of the following rules: * <ul> * <li>The identification variable is omitted if it's not defined in the <b>FROM</b> clause; * <li>The last path is a state field; * <li>Only embedded field can be traversed. * </ul> * * @param expression {@link UpdateItem} to validate its path expression * @return <code>true</code> if the path expression is valid; <code>false</code> otherwise */ protected boolean validateUpdateItem(UpdateItem expression) { boolean valid = true; if (expression.hasStateFieldPathExpression()) { // Retrieve the state field path expression StateFieldPathExpression pathExpression = getStateFieldPathExpression(expression.getStateFieldPathExpression()); if ((pathExpression != null) && (pathExpression.hasIdentificationVariable() || pathExpression.hasVirtualIdentificationVariable())) { // Start traversing the path expression by first retrieving the managed type Object managedType = helper.getManagedType(pathExpression.getIdentificationVariable()); if (managedType != null) { // Continue to traverse the path expression for (int index = pathExpression.hasVirtualIdentificationVariable() ? 0 : 1, count = pathExpression.pathSize(); index < count; index++) { // Retrieve the mapping String path = pathExpression.getPath(index); Object mapping = helper.getMappingNamed(managedType, path); // Not resolvable if (mapping == null) { addProblem(pathExpression, UpdateItem_NotResolvable, pathExpression.toParsedText()); valid = false; break; } // A collection mapping cannot be traversed or a value cannot be assigned to it else if (helper.isCollectionMapping(mapping)) { addProblem(pathExpression, UpdateItem_RelationshipPathExpression); valid = false; break; } // Validate an intermediate path (n + 1, ..., n - 2) else if (index + 1 < count) { // A relationship mapping cannot be traversed if (helper.isRelationshipMapping(mapping)) { addProblem(pathExpression, UpdateItem_RelationshipPathExpression); valid = false; break; } // A basic mapping cannot be traversed else if (helper.isPropertyMapping(mapping)) { addProblem(pathExpression, UpdateItem_RelationshipPathExpression); valid = false; break; } // Retrieve the embeddable mapping's reference managed type else { managedType = helper.getReferenceManagedType(mapping); if (managedType == null) { addProblem(pathExpression, UpdateItem_NotResolvable, pathExpression.toParsedText()); valid = false; break; } } } } } else { addProblem(pathExpression, UpdateItem_NotResolvable, pathExpression.toParsedText()); valid = false; } } else { addProblem(pathExpression, UpdateItem_NotResolvable, expression.getStateFieldPathExpression().toParsedText()); valid = false; } } return valid; } /** * Validates the given {@link UpdateStatement}. The default behavior does not require to * semantically validate it. * * @param expression The {@link UpdateStatement} to validate */ protected void validateUpdateStatement(UpdateStatement expression) { super.visit(expression); } /** * Validates the encapsulated expression of the given <code><b>UPPER</b></code> expression. The * test to perform is: * <ul> * <li>If the encapsulated expression is a path expression, validation makes sure it is a basic * mapping, an association field is not allowed.</li> * <li>If the encapsulated expression is not a path expression, validation will be redirected to * that expression but the returned status will not be changed.</li> * </ul> * * @param expression The {@link UpperExpression} to validate by validating its encapsulated expression * @return <code>false</code> if the encapsulated expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateUpperExpression(UpperExpression expression) { return validateFunctionPathExpression(expression); } /** * Validates the given {@link ValueExpression}. The default behavior does not require to * semantically validate it. * * @param expression The {@link ValueExpression} to validate */ protected void validateValueExpression(ValueExpression expression) { super.visit(expression); } /** * Validates the given {@link WhenClause}. The default behavior does not require to semantically * validate it. * * @param expression The {@link WhenClause} to validate */ protected void validateWhenClause(WhenClause expression) { super.visit(expression); } /** * Validates the given {@link WhereClause}. The default behavior does not require to semantically * validate it. * * @param expression The {@link WhereClause} to validate */ protected void validateWhereClause(WhereClause expression) { super.visit(expression); } /** * Returns the type of path expression that is valid for a count function; which is the left * expression in a <code><b>COUNT</b></code> expression. * * @return By default, any field are allowed except collection */ protected PathType validPathExpressionTypeForCountFunction() { return PathType.ANY_FIELD; } /** * Returns the type of path expression that is valid for the expression being tested by an * <code><b>IN</b></code> expression; which is the left expression. * * @return By default, any field (without collection) is allowed */ protected PathType validPathExpressionTypeForInExpression() { return PathType.ANY_FIELD; } /** * Returns the type of path expression that is valid for an <code>IN</code> items; which is the * left expression in a <code><b>IN</b></code> expression. * * @return By default, any field are allowed except collection */ protected PathType validPathExpressionTypeForInItem() { return PathType.ANY_FIELD; } /** * Returns the type of path expression that is valid for a string expression; which is the left * expression in a <code><b>LIKE</b></code> expression. * * @return By default, only basic field are allowed */ protected PathType validPathExpressionTypeForStringExpression() { return PathType.BASIC_FIELD_ONLY; } /** * {@inheritDoc} */ @Override public final void visit(AbsExpression expression) { validateAbsExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(AbstractSchemaName expression) { validateAbstractSchemaName(expression); } /** * {@inheritDoc} */ @Override public final void visit(AdditionExpression expression) { validateAdditionExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(AllOrAnyExpression expression) { validateAllOrAnyExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(AndExpression expression) { validateAndExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(ArithmeticFactor expression) { validateArithmeticExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(AvgFunction expression) { validateAvgFunction(expression); } /** * {@inheritDoc} */ @Override public final void visit(BadExpression expression) { // Nothing to validate semantically } /** * {@inheritDoc} */ @Override public final void visit(BetweenExpression expression) { validateBetweenExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(CaseExpression expression) { validateCaseExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(CoalesceExpression expression) { validateCoalesceExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(CollectionExpression expression) { // Nothing to validate semantically super.visit(expression); } /** * {@inheritDoc} */ @Override public final void visit(CollectionMemberDeclaration expression) { validateCollectionMemberDeclaration(expression); } /** * {@inheritDoc} */ @Override public final void visit(CollectionMemberExpression expression) { validateCollectionMemberExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(CollectionValuedPathExpression expression) { // Validated by the parent of the expression } /** * {@inheritDoc} */ @Override public final void visit(ComparisonExpression expression) { validateComparisonExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(ConcatExpression expression) { validateConcatExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(ConstructorExpression expression) { validateConstructorExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(CountFunction expression) { validateCountFunction(expression); } /** * {@inheritDoc} */ @Override public final void visit(DateTime expression) { validateDateTime(expression); } /** * {@inheritDoc} */ @Override public final void visit(DeleteClause expression) { validateDeleteClause(expression); } /** * {@inheritDoc} */ @Override public final void visit(DeleteStatement expression) { validateDeleteStatement(expression); } /** * {@inheritDoc} */ @Override public final void visit(DivisionExpression expression) { validateDivisionExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(EmptyCollectionComparisonExpression expression) { validateCollectionValuedPathExpression(expression.getExpression(), true); } /** * {@inheritDoc} */ @Override public final void visit(EntityTypeLiteral expression) { validateEntityTypeLiteral(expression); } /** * {@inheritDoc} */ @Override public final void visit(EntryExpression expression) { validateEntryExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(ExistsExpression expression) { validateExistsExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(FromClause expression) { validateFromClause(expression); } /** * {@inheritDoc} */ @Override public final void visit(FunctionExpression expression) { validateFunctionExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(GroupByClause expression) { validateGroupByClause(expression); } /** * {@inheritDoc} */ @Override public final void visit(HavingClause expression) { validateHavingClause(expression); } /** * {@inheritDoc} */ @Override public final void visit(IdentificationVariable expression) { validateIdentificationVariable(expression); } /** * {@inheritDoc} */ @Override public final void visit(IdentificationVariableDeclaration expression) { validateIdentificationVariableDeclaration(expression); } /** * {@inheritDoc} */ @Override public final void visit(IndexExpression expression) { validateIndexExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(InExpression expression) { validateInExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(InputParameter expression) { // Nothing to validate semantically } /** * {@inheritDoc} */ @Override public final void visit(Join expression) { validateJoin(expression); } /** * {@inheritDoc} */ @Override public final void visit(JPQLExpression expression) { if (expression.hasQueryStatement()) { expression.getQueryStatement().accept(this); validateIdentificationVariables(); } } /** * {@inheritDoc} */ @Override public final void visit(KeyExpression expression) { validateKeyExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(KeywordExpression expression) { // Nothing semantically to validate } /** * {@inheritDoc} */ @Override public final void visit(LengthExpression expression) { validateLengthExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(LikeExpression expression) { validateLikeExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(LocateExpression expression) { validateLocateExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(LowerExpression expression) { validateLowerExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(MaxFunction expression) { validateMaxFunction(expression); } /** * {@inheritDoc} */ @Override public final void visit(MinFunction expression) { validateMinFunction(expression); } /** * {@inheritDoc} */ @Override public final void visit(ModExpression expression) { validateModExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(MultiplicationExpression expression) { validateMultiplicationExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(NotExpression expression) { validateNotExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(NullComparisonExpression expression) { validateNullComparisonExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(NullExpression expression) { // Nothing semantically to validate } /** * {@inheritDoc} */ @Override public final void visit(NullIfExpression expression) { validateNullIfExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(NumericLiteral expression) { // Nothing semantically to validate } /** * {@inheritDoc} */ @Override public final void visit(ObjectExpression expression) { validateObjectExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(OnClause expression) { validateOnClause(expression); } /** * {@inheritDoc} */ @Override public final void visit(OrderByClause expression) { validateOrderByClause(expression); } /** * {@inheritDoc} */ @Override public final void visit(OrderByItem expression) { validateOrderByItem(expression); } /** * {@inheritDoc} */ @Override public final void visit(OrExpression expression) { validateOrExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(RangeVariableDeclaration expression) { validateRangeVariableDeclaration(expression); } /** * {@inheritDoc} */ @Override public final void visit(ResultVariable expression) { try { registerIdentificationVariable = false; validateResultVariable(expression); } finally { registerIdentificationVariable = true; } } /** * {@inheritDoc} */ @Override public final void visit(SelectClause expression) { validateSelectClause(expression); } /** * {@inheritDoc} */ @Override public final void visit(SelectStatement expression) { validateSelectStatement(expression); } /** * {@inheritDoc} */ @Override public final void visit(SimpleFromClause expression) { validateSimpleFromClause(expression); } /** * {@inheritDoc} */ @Override public final void visit(SimpleSelectClause expression) { validateSimpleSelectClause(expression); } /** * {@inheritDoc} */ @Override public final void visit(SimpleSelectStatement expression) { validateSimpleSelectStatement(expression); } /** * {@inheritDoc} */ @Override public final void visit(SizeExpression expression) { validateSizeExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(SqrtExpression expression) { validateSqrtExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(StateFieldPathExpression expression) { validateStateFieldPathExpression(expression, PathType.ANY_FIELD); } /** * {@inheritDoc} */ @Override public final void visit(StringLiteral expression) { // Nothing semantically to validate } /** * {@inheritDoc} */ @Override public final void visit(SubExpression expression) { // Nothing semantically to validate super.visit(expression); } /** * {@inheritDoc} */ @Override public final void visit(SubstringExpression expression) { validateSubstringExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(SubtractionExpression expression) { validateSubtractionExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(SumFunction expression) { validateSumFunction(expression); } /** * {@inheritDoc} */ @Override public final void visit(TreatExpression expression) { validateTreatExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(TrimExpression expression) { validateTrimExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(TypeExpression expression) { validateTypeExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(UnknownExpression expression) { // Nothing semantically to validate } /** * {@inheritDoc} */ @Override public final void visit(UpdateClause expression) { validateUpdateClause(expression); } /** * {@inheritDoc} */ @Override public final void visit(UpdateItem expression) { validateUpdateItem(expression); } /** * {@inheritDoc} */ @Override public final void visit(UpdateStatement expression) { validateUpdateStatement(expression); } /** * {@inheritDoc} */ @Override public final void visit(UpperExpression expression) { validateUpperExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(ValueExpression expression) { validateValueExpression(expression); } /** * {@inheritDoc} */ @Override public final void visit(WhenClause expression) { validateWhenClause(expression); } /** * {@inheritDoc} */ @Override public final void visit(WhereClause expression) { validateWhereClause(expression); } // Made static final for performance reasons. /** * This visitor is meant to retrieve an {@link CollectionValuedPathExpression} if the visited * {@link Expression} is that object. */ protected static final class CollectionValuedPathExpressionVisitor extends AbstractExpressionVisitor { /** * The {@link CollectionValuedPathExpression} that was visited; <code>null</code> if he was not. */ protected CollectionValuedPathExpression expression; /** * {@inheritDoc} */ @Override public void visit(CollectionValuedPathExpression expression) { this.expression = expression; } } // Made static final for performance reasons. protected static final class ComparingEntityTypeLiteralVisitor extends AbstractExpressionVisitor { protected IdentificationVariable expression; public boolean result; /** * {@inheritDoc} */ @Override public void visit(ComparisonExpression expression) { result = true; } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariable expression) { if (this.expression == expression) { expression.getParent().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(SubExpression expression) { // Make sure to bypass any sub expression expression.getParent().accept(this); } } // Made static final for performance reasons. /** * This visitor compares the left and right expressions of a comparison expression and gathers * information about those expressions if they are an identification variable or a path expression. */ protected static final class ComparisonExpressionVisitor extends AnonymousExpressionVisitor { private final AbstractSemanticValidator validator; public boolean leftIdentificationVariable; public boolean leftIdentificationVariableValid; public boolean leftStateFieldPathExpression; public boolean leftStateFieldPathExpressionValid; public boolean rightIdentificationVariable; public boolean rightIdentificationVariableValid; public boolean rightStateFieldPathExpression; public boolean rightStateFieldPathExpressionValid; public boolean validatingLeftExpression; private ComparisonExpressionVisitor(AbstractSemanticValidator validator) { this.validator = validator; } /** * Resets the flags. */ private void dispose() { leftIdentificationVariable = false; leftIdentificationVariableValid = false; leftStateFieldPathExpression = false; leftStateFieldPathExpressionValid = false; rightIdentificationVariable = false; rightIdentificationVariableValid = false; rightStateFieldPathExpression = false; rightStateFieldPathExpressionValid = false; } /** * {@inheritDoc} */ @Override protected void visit(Expression expression) { // Redirect to the validator, nothing special is required expression.accept(validator); } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariable expression) { // Make sure the identification variable is not a result variable if (!validator.helper.isResultVariable(expression.getVariableName())) { if (validatingLeftExpression) { leftIdentificationVariable = !expression.isVirtual(); // Make sure what was parsed is a valid identification variable leftIdentificationVariableValid = validator.validateIdentificationVariable(expression); } else { rightIdentificationVariable = !expression.isVirtual(); // Make sure what was parsed is a valid identification variable rightIdentificationVariableValid = validator.validateIdentificationVariable(expression); } } } /** * {@inheritDoc} */ @Override public void visit(StateFieldPathExpression expression) { if (validatingLeftExpression) { leftStateFieldPathExpression = true; // Make sure what was parsed is a valid path expression leftStateFieldPathExpressionValid = validator.validateStateFieldPathExpression( expression, PathType.ANY_FIELD_INCLUDING_COLLECTION ); } else { rightStateFieldPathExpression = true; // Make sure what was parsed is a valid path expression rightStateFieldPathExpressionValid = validator.validateStateFieldPathExpression( expression, PathType.ANY_FIELD_INCLUDING_COLLECTION ); } } } // Made static for performance reasons. protected static class FirstDeclarationVisitor extends AnonymousExpressionVisitor { protected boolean valid; /** * {@inheritDoc} */ @Override public void visit(AbstractSchemaName expression) { valid = true; } /** * {@inheritDoc} */ @Override public void visit(BadExpression expression) { // Already validated, no need to duplicate the issue valid = false; } /** * {@inheritDoc} */ @Override protected void visit(Expression expression) { valid = false; } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariableDeclaration expression) { expression.getRangeVariableDeclaration().accept(this); } /** * {@inheritDoc} */ @Override public void visit(NullExpression expression) { // Already validated, no need to duplicate the issue valid = false; } /** * {@inheritDoc} */ @Override public void visit(RangeVariableDeclaration expression) { expression.getRootObject().accept(this); } } // Made static final for performance reasons. protected static final class InItemsVisitor extends AnonymousExpressionVisitor { private final AbstractSemanticValidator validator; protected InItemsVisitor(AbstractSemanticValidator validator) { this.validator = validator; } /** * {@inheritDoc} */ @Override protected void visit(Expression expression) { expression.accept(validator); } /** * {@inheritDoc} */ @Override public void visit(StateFieldPathExpression expression) { validator.validateStateFieldPathExpression(expression, validator.validPathExpressionTypeForInItem()); } } // Made static for performance reasons. /** * This enumeration allows {@link AbstractSemanticValidator#validateStateFieldPathExpression( * StateFieldPathExpression, PathType)} to validate the type of the mapping and to make sure it * is allowed based on its location. */ protected static enum PathType { /** * This will allow basic, and association fields to be specified. */ ANY_FIELD, /** * This will allow basic, and association fields to be specified. */ ANY_FIELD_INCLUDING_COLLECTION, /** * This will allow association fields to be specified but basic mappings are not valid. */ ASSOCIATION_FIELD_ONLY, /** * This will allow basic fields to be specified but association mappings are not valid. */ BASIC_FIELD_ONLY } // Made static final for performance reasons. /** * This visitor is meant to retrieve an {@link StateFieldPathExpressionVisitor} if the visited * {@link Expression} is that object. */ protected static final class StateFieldPathExpressionVisitor extends AbstractExpressionVisitor { /** * The {@link StateFieldPathExpression} that was visited; <code>null</code> if it was not. */ protected StateFieldPathExpression expression; /** * {@inheritDoc} */ @Override public void visit(StateFieldPathExpression expression) { this.expression = expression; } } // Made static final for performance reasons. protected static final class SubqueryFirstDeclarationVisitor extends FirstDeclarationVisitor { /** * {@inheritDoc} */ @Override public void visit(CollectionValuedPathExpression expression) { valid = true; } } // Made static for performance reasons. protected static class TopLevelFirstDeclarationVisitor extends FirstDeclarationVisitor { /** * {@inheritDoc} */ @Override public void visit(AbstractSchemaName expression) { // TODO valid = true; } } }