/******************************************************************************* * 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.tools; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.AbsExpression_InvalidNumericExpression; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.AvgFunction_InvalidNumericExpression; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.BetweenExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.CollectionMemberExpression_Embeddable; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ComparisonExpression_WrongComparisonType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ConcatExpression_Expression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ConstructorExpression_UndefinedConstructor; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ConstructorExpression_UnknownType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.CountFunction_DistinctEmbeddable; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.EncapsulatedIdentificationVariableExpression_NotMapValued; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.IdentificationVariable_EntityName; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LengthExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LocateExpression_FirstExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LocateExpression_SecondExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LocateExpression_ThirdExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LowerExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ModExpression_FirstExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ModExpression_SecondExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.NotExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.NullComparisonExpression_InvalidType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SqrtExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SubstringExpression_FirstExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SubstringExpression_SecondExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SubstringExpression_ThirdExpression_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SumFunction_WrongType; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpdateItem_NotAssignable; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpdateItem_NotResolvable; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpdateItem_NullNotAssignableToPrimitive; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpperExpression_WrongType; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.persistence.jpa.jpql.AbstractSemanticValidator; import org.eclipse.persistence.jpa.jpql.ExpressionTools; import org.eclipse.persistence.jpa.jpql.ITypeHelper; import org.eclipse.persistence.jpa.jpql.JPAVersion; import org.eclipse.persistence.jpa.jpql.LiteralType; import org.eclipse.persistence.jpa.jpql.LiteralVisitor; import org.eclipse.persistence.jpa.jpql.SemanticValidatorHelper; import org.eclipse.persistence.jpa.jpql.parser.AbsExpression; import org.eclipse.persistence.jpa.jpql.parser.AbstractExpression; import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor; import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName; 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.ArithmeticExpression; import org.eclipse.persistence.jpa.jpql.parser.ArithmeticFactor; import org.eclipse.persistence.jpa.jpql.parser.AvgFunction; import org.eclipse.persistence.jpa.jpql.parser.BetweenExpression; import org.eclipse.persistence.jpa.jpql.parser.BooleanPrimaryBNF; 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.CollectionMemberExpression; import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression; 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.DivisionExpression; import org.eclipse.persistence.jpa.jpql.parser.EmptyCollectionComparisonExpression; import org.eclipse.persistence.jpa.jpql.parser.EncapsulatedIdentificationVariableExpression; 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.IdentificationVariable; import org.eclipse.persistence.jpa.jpql.parser.IndexExpression; import org.eclipse.persistence.jpa.jpql.parser.InputParameter; import org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF; 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.LiteralBNF; 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.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.SimpleArithmeticExpressionBNF; 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.StringPrimaryBNF; 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.TrimExpression; import org.eclipse.persistence.jpa.jpql.parser.UpdateClause; import org.eclipse.persistence.jpa.jpql.parser.UpdateItem; import org.eclipse.persistence.jpa.jpql.parser.UpperExpression; import org.eclipse.persistence.jpa.jpql.parser.ValueExpression; /** * This validator is responsible to gather the problems found in a JPQL query by validating the * content to make sure it is semantically valid. The grammar is not validated by this visitor. * <p> * For instance, the function <b>AVG</b> accepts a state field path. The property it represents has * to be of numeric type. <b>AVG(e.name)</b> is parsable but is not semantically valid because the * type of name is a string (the property signature is: "<code>private String name</code>"). * <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 DefaultGrammarValidator * * @version 2.5 * @since 2.3 * @author Pascal Filion */ @SuppressWarnings("nls") public class DefaultSemanticValidator extends AbstractSemanticValidator { /** * This validator determines whether the {@link Expression} visited represents * {@link Expression#NULL}. */ protected NullValueVisitor nullValueVisitor; /** * This finder is responsible to retrieve the abstract schema name from the <b>UPDATE</b> range * declaration expression. */ protected UpdateClauseAbstractSchemaNameFinder updateClauseAbstractSchemaNameFinder; /** * The {@link TypeValidator TypeVlidators} mapped to their Java class. Those validators validate * any {@link Expression} by making sure its type matches the desired type. */ protected Map<Class<? extends TypeValidator>, TypeValidator> validators; /** * Creates a new <code>DefaultSemanticValidator</code>. * * @param queryContext The context used to query information about the JPQL query * @exception NullPointerException The given {@link JPQLQueryContext} cannot be <code>null</code> */ public DefaultSemanticValidator(JPQLQueryContext queryContext) { super(new GenericSemanticValidatorHelper(queryContext)); } /** * Creates a new <code>DefaultSemanticValidator</code>. * * @param helper The given helper allows the validator to access the JPA artifacts without using * Hermes SPI directly * @exception NullPointerException The given {@link SemanticValidatorHelper} cannot be <code>null</code> * @since 2.4 */ public DefaultSemanticValidator(SemanticValidatorHelper helper) { super(helper); } protected boolean areTypesEquivalent(Object[] typeDeclarations1, Object[] typeDeclarations2) { // Empty array if ((typeDeclarations1.length == 0) && (typeDeclarations2.length == 0)) { return true; } // Different array length, always not equivalent if (typeDeclarations1.length != typeDeclarations2.length) { return false; } // Compare each element of the array together for (int index = typeDeclarations1.length; --index >= 0; ) { if (!helper.isTypeDeclarationAssignableTo(typeDeclarations1[index], typeDeclarations2[index])) { return false; } } return true; } /** * {@inheritDoc} */ @Override protected LiteralVisitor buildLiteralVisitor() { return new DefaultLiteralVisitor(); } /** * {@inheritDoc} */ @Override protected OwningClauseVisitor buildOwningClauseVisitor() { return new OwningClauseVisitor(); } protected ResultVariableInOrderByVisitor buildResultVariableInOrderByVisitor() { return new ResultVariableInOrderByVisitor(); } protected AbstractSchemaName findAbstractSchemaName(UpdateItem expression) { UpdateClauseAbstractSchemaNameFinder visitor = getUpdateClauseAbstractSchemaNameFinder(); try { expression.accept(visitor); return visitor.expression; } finally { visitor.expression = null; } } protected NullValueVisitor getNullValueVisitor() { if (nullValueVisitor == null) { nullValueVisitor = new NullValueVisitor(); } return nullValueVisitor; } protected Object getType(Expression expression) { return helper.getType(expression); } protected ITypeHelper getTypeHelper() { return helper.getTypeHelper(); } protected UpdateClauseAbstractSchemaNameFinder getUpdateClauseAbstractSchemaNameFinder() { if (updateClauseAbstractSchemaNameFinder == null) { updateClauseAbstractSchemaNameFinder = new UpdateClauseAbstractSchemaNameFinder(); } return updateClauseAbstractSchemaNameFinder; } protected TypeValidator getValidator(Class<? extends TypeValidator> validatorClass) { TypeValidator validator = validators.get(validatorClass); if (validator == null) { try { Constructor<? extends TypeValidator> constructor = validatorClass.getDeclaredConstructor(DefaultSemanticValidator.class); constructor.setAccessible(true); validator = constructor.newInstance(this); validators.put(validatorClass, validator); } catch (ReflectiveOperationException e) { /* Never happens */ } } return validator; } /** * {@inheritDoc} */ @Override protected void initialize() { super.initialize(); validators = new HashMap<Class<? extends TypeValidator>, TypeValidator>(); } /** * Determines whether the given {@link Expression} is of the correct type based on these rules: * <ul> * <li>The {@link Expression} returns a boolean value;</li> * </ul> * * @param expression The {@link Expression} to validate * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code> * otherwise */ protected boolean isBooleanType(Expression expression) { return isValid(expression, BooleanTypeValidator.class); } protected boolean isComparisonEquivalentType(Expression expression1, Expression expression2) { Object type1 = helper.getType(expression1); Object type2 = helper.getType(expression2); // 1. The types are the same // 2. The type cannot be determined, pretend they are equivalent, // another rule will validate it // 3. One is assignable to the other one ITypeHelper typeHelper = getTypeHelper(); return (type1 == type2) || !helper.isTypeResolvable(type1) || !helper.isTypeResolvable(type2) || typeHelper.isNumericType(type1) && typeHelper.isNumericType(type2) || typeHelper.isDateType(type1) && typeHelper.isDateType(type2) || helper.isAssignableTo(type1, type2) || helper.isAssignableTo(type2, type1); } protected boolean isEquivalentBetweenType(Expression expression1, Expression expression2) { Object type1 = helper.getType(expression1); Object type2 = helper.getType(expression2); // The type cannot be determined, pretend they are equivalent, // another rule will validate it if (!helper.isTypeResolvable(type1) || !helper.isTypeResolvable(type2)) { return true; } ITypeHelper typeHelper = getTypeHelper(); if (type1 == type2) { return typeHelper.isNumericType(type1) || typeHelper.isStringType(type1) || typeHelper.isDateType(type1); } else { return typeHelper.isNumericType(type1) && typeHelper.isNumericType(type2) || typeHelper.isStringType(type1) && typeHelper.isStringType(type2) || typeHelper.isDateType(type1) && typeHelper.isDateType(type2); } } /** * Determines whether the given {@link Expression} is of the correct type based on these rules: * <ul> * <li>The {@link Expression}'s type is an integral type (long or integer).</li> * </ul> * * @param expression The {@link Expression} to validate * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code> * otherwise */ protected boolean isIntegralType(Expression expression) { if (isNumericType(expression)) { ITypeHelper typeHelper = getTypeHelper(); Object type = helper.getType(expression); return type == typeHelper.unknownType() || typeHelper.isIntegralType(type); } return false; } protected boolean isNullValue(Expression expression) { NullValueVisitor visitor = getNullValueVisitor(); try { expression.accept(visitor); return visitor.valid; } finally { visitor.valid = false; } } /** * Determines whether the given {@link Expression} is of the correct type based on these rules: * <ul> * <li>The {@link Expression} returns a numeric value;</li> * </ul> * * @param expression The {@link Expression} to validate * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code> * otherwise */ protected boolean isNumericType(Expression expression) { return isValid(expression, NumericTypeValidator.class); } /** * Determines whether the given {@link Expression} is of the correct type based on these rules: * <ul> * <li>The {@link Expression}'s type is a string type.</li> * </ul> * * @param expression The {@link Expression} to validate * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code> * otherwise */ protected boolean isStringType(Expression expression) { return isValid(expression, StringTypeValidator.class); } /** * Determines whether the given {@link Expression} is of the correct type by using the * {@link TypeValidator}. * * @param expression The {@link Expression} to validate * @param validatorClass The Java class of the {@link TypeValidator} that will determine if the * given {@link Expression} has the right type * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code> * otherwise */ protected boolean isValid(Expression expression, Class<? extends TypeValidator> validatorClass) { TypeValidator validator = getValidator(validatorClass); try { expression.accept(validator); return validator.valid; } finally { validator.valid = false; } } protected boolean isValidWithFindQueryBNF(AbstractExpression expression, String queryBNF) { JPQLQueryBNFValidator validator = getExpressionValidator(queryBNF); try { JPQLQueryBNF childQueryBNF = expression.getParent().findQueryBNF(expression); validator.validate(childQueryBNF); return validator.isValid(); } finally { validator.dispose(); } } /** * {@inheritDoc} */ @Override protected PathType selectClausePathExpressionPathType() { return PathType.ANY_FIELD; } /** * {@inheritDoc} */ @Override protected boolean validateAbsExpression(AbsExpression expression) { boolean valid = super.validateAbsExpression(expression); if (valid) { // The ABS function takes a numeric argument valid = validateNumericType(expression.getExpression(), AbsExpression_InvalidNumericExpression); } return valid; } /** * {@inheritDoc} */ @Override protected int validateArithmeticExpression(ArithmeticExpression expression, String leftExpressionWrongTypeMessageKey, String rightExpressionWrongTypeMessageKey) { int result = super.validateArithmeticExpression( expression, leftExpressionWrongTypeMessageKey, rightExpressionWrongTypeMessageKey ); // Only validate the left expression if it's still valid if (isValid(result, 0)) { boolean valid = validateNumericType(expression.getLeftExpression(), leftExpressionWrongTypeMessageKey); updateStatus(result, 0, valid); } // Validate the right expression boolean valid = validateNumericType(expression.getRightExpression(), rightExpressionWrongTypeMessageKey); updateStatus(result, 1, valid); return result; } /** * {@inheritDoc} */ @Override protected boolean validateAvgFunction(AvgFunction expression) { // Arguments to the functions AVG must be numeric boolean valid = super.validateAvgFunction(expression); if (valid) { valid = validateNumericType(expression.getExpression(), AvgFunction_InvalidNumericExpression); } return valid; } /** * {@inheritDoc} */ @Override protected int validateBetweenExpression(BetweenExpression expression) { int result = super.validateBetweenExpression(expression); // Only add extra validation if the lower and upper expressions are still valid if (isValid(result, 1) && isValid(result, 2) && !isEquivalentBetweenType(expression.getExpression(), expression.getLowerBoundExpression()) || !isEquivalentBetweenType(expression.getExpression(), expression.getUpperBoundExpression())) { addProblem(expression, BetweenExpression_WrongType); updateStatus(result, 1, false); updateStatus(result, 2, false); } return result; } /** * Determines whether the given {@link Expression} is of the correct type based on these rules: * <ul> * <li>The {@link Expression} returns a boolean value;</li> * <li>The {@link Expression}'s type is a boolean type.</li> * </ul> * * @param expression The {@link Expression} to validate * @param messageKey The key used to retrieve the localized message describing the problem */ protected boolean validateBooleanType(Expression expression, String messageKey) { if (isValid(expression, BooleanPrimaryBNF.ID) && !isBooleanType(expression)) { addProblem(expression, messageKey); return false; } return true; } /** * {@inheritDoc} */ @Override protected int validateCollectionMemberExpression(CollectionMemberExpression expression) { int result = super.validateCollectionMemberExpression(expression); if (isValid(result, 0) && expression.hasEntityExpression()) { Expression entityExpression = expression.getEntityExpression(); // Check for embeddable type Object type = helper.getType(entityExpression); if (helper.getEmbeddable(type) != null) { addProblem(entityExpression, CollectionMemberExpression_Embeddable); updateStatus(result, 0, false); } } return result; } /** * {@inheritDoc} */ @Override protected boolean validateComparisonExpression(ComparisonExpression expression) { boolean valid = super.validateComparisonExpression(expression); // Only validate the left and right if they are still valid if (valid && expression.hasLeftExpression() && expression.hasRightExpression()) { // The result of the two expressions must be like that of the other argument // Comparisons over instances of embeddable class or map entry types are not supported if (!isComparisonEquivalentType(expression.getLeftExpression(), expression.getRightExpression())) { addProblem(expression, ComparisonExpression_WrongComparisonType); valid = false; } } return valid; } /** * {@inheritDoc} */ @Override protected boolean validateConcatExpression(ConcatExpression expression) { boolean result = super.validateConcatExpression(expression); // The CONCAT function returns a string that is a concatenation of its arguments if (expression.hasExpression()) { int index = 0; for (Expression child : getChildren(expression.getExpression())) { // Don't validate the first expression if it is not valid if (index != 0 || result) { result &= validateStringType(child, ConcatExpression_Expression_WrongType); } index++; } } return result; } /** * {@inheritDoc} */ @Override protected void validateConstructorExpression(ConstructorExpression expression) { super.validateConstructorExpression(expression); String className = expression.getClassName(); // Only test the constructor if it has been specified if (ExpressionTools.stringIsNotEmpty(className)) { Object type = helper.getType(className); // Unknown type if (!helper.isTypeResolvable(type)) { int startPosition = position(expression) + 4 /* NEW + whitespace */; int endPosition = startPosition + className.length(); addProblem(expression, startPosition, endPosition, ConstructorExpression_UnknownType, className); } // Test the arguments' types with the constructors' types else if (expression.hasLeftParenthesis() && expression.hasConstructorItems()) { Expression constructorItems = expression.getConstructorItems(); // Retrieve the constructor's arguments so their type can be calculated List<Expression> children = getChildren(constructorItems); Object[] typeDeclarations = null; boolean constructorFound = false; for (Object constructor : helper.getConstructors(type)) { Object[] parameterTypeDeclarations = helper.getMethodParameterTypeDeclarations(constructor); // The number of items match, check their types are equivalent if (children.size() == parameterTypeDeclarations.length) { // The constructor is the default constructor and both have no parameters if (parameterTypeDeclarations.length == 0) { constructor = true; } else { // Populate the type declaration array if (typeDeclarations == null) { typeDeclarations = new Object[children.size()]; for (int index = children.size(); --index >= 0; ) { typeDeclarations[index] = helper.getTypeDeclaration(children.get(index)); } } constructorFound = areTypesEquivalent(parameterTypeDeclarations, typeDeclarations); if (constructorFound) { break; } } } } // No constructor was found matching the argument list if (!constructorFound) { int startPosition = position(expression) + 4 /* NEW + whitespace */; int endPosition = startPosition + className.length(); addProblem(expression, startPosition, endPosition, ConstructorExpression_UndefinedConstructor); } } } } /** * {@inheritDoc} */ @Override protected void validateCountFunction(CountFunction expression) { super.validateCountFunction(expression); // The use of DISTINCT with COUNT is not supported for arguments of // embeddable types or map entry types (weird because map entry is not // allowed in a COUNT expression) if (expression.hasExpression() && expression.hasDistinct()) { Expression childExpression = expression.getExpression(); // Check for embeddable type Object type = helper.getType(childExpression); if (helper.getEmbeddable(type) != null) { int distinctLength = Expression.DISTINCT.length() + 1; // +1 = space int startIndex = position(childExpression) - distinctLength; int endIndex = startIndex + length(childExpression) + distinctLength; addProblem(expression, startIndex, endIndex, CountFunction_DistinctEmbeddable); } } } /** * {@inheritDoc} */ @Override protected void validateEntryExpression(EntryExpression expression) { validateMapIdentificationVariable(expression); super.validateEntryExpression(expression); } /** * {@inheritDoc} */ @Override protected boolean validateIdentificationVariable(IdentificationVariable expression, String variable) { boolean valid = super.validateIdentificationVariable(expression, variable); for (String entityName : helper.entityNames()) { // An identification variable must not have the same name as any entity in the same // persistence unit, unless it's representing an entity literal if (variable.equalsIgnoreCase(entityName)) { // An identification variable could represent an entity type literal, // validate the parent to make sure it allows it if (!isValidWithFindQueryBNF(expression, LiteralBNF.ID)) { int startIndex = position(expression); int endIndex = startIndex + variable.length(); addProblem(expression, startIndex, endIndex, IdentificationVariable_EntityName); valid = false; break; } } } return valid; } /** * Determines whether the given {@link Expression} is of the correct type based on these rules: * <ul> * <li>The {@link Expression} returns a integral value;</li> * <li>The {@link Expression}'s type is an integral type (long or integer).</li> * </ul> * * @param expression The {@link Expression} to validate * @param queryBNF The unique identifier of the query BNF used to validate the type * @param messageKey The key used to retrieve the localized message describing the problem * @return <code>false</code> if the given expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateIntegralType(Expression expression, String queryBNF, String messageKey) { if (isValid(expression, queryBNF) && !isIntegralType(expression)) { addProblem(expression, messageKey); return false; } return true; } /** * {@inheritDoc} */ @Override protected void validateKeyExpression(KeyExpression expression) { validateMapIdentificationVariable(expression); super.validateKeyExpression(expression); } /** * {@inheritDoc} */ @Override protected boolean validateLengthExpression(LengthExpression expression) { boolean valid = super.validateLengthExpression(expression); if (valid) { valid = validateStringType(expression.getExpression(), LengthExpression_WrongType); } return valid; } /** * {@inheritDoc} */ @Override protected int validateLocateExpression(LocateExpression expression) { int result = super.validateLocateExpression(expression); // The first argument is the string to be located; the second argument is the string to be // searched; the optional third argument is an integer that represents the string position at // which the search is started (by default, the beginning of the string to be searched) if (isValid(result, 0)) { boolean valid = validateStringType(expression.getFirstExpression(), LocateExpression_FirstExpression_WrongType); updateStatus(result, 0, valid); } if (isValid(result, 1)) { boolean valid = validateStringType (expression.getSecondExpression(), LocateExpression_SecondExpression_WrongType); updateStatus(result, 1, valid); } if (isValid(result, 2)) { boolean valid = validateNumericType(expression.getThirdExpression(), LocateExpression_ThirdExpression_WrongType); updateStatus(result, 2, valid); } return result; } /** * {@inheritDoc} */ @Override protected boolean validateLowerExpression(LowerExpression expression) { boolean valid = super.validateLowerExpression(expression); if (valid) { valid = validateStringType(expression.getExpression(), LowerExpression_WrongType); } return valid; } protected void validateMapIdentificationVariable(EncapsulatedIdentificationVariableExpression expression) { // The KEY, VALUE, and ENTRY operators may only be applied to // identification variables that correspond to map-valued associations // or map-valued element collections if (expression.hasExpression()) { Expression childExpression = expression.getExpression(); String variableName = literal(childExpression, LiteralType.IDENTIFICATION_VARIABLE); // Retrieve the identification variable's type without traversing the type parameters if (ExpressionTools.stringIsNotEmpty(variableName)) { Object typeDeclaration = helper.getTypeDeclaration(childExpression); Object type = helper.getType(typeDeclaration); if (!getTypeHelper().isMapType(type)) { addProblem( childExpression, EncapsulatedIdentificationVariableExpression_NotMapValued, expression.getIdentifier() ); } } } } /** * {@inheritDoc} */ @Override protected int validateModExpression(ModExpression expression) { int result = super.validateModExpression(expression); // Don't validate the first expression if it's not valid if (isValid(result, 0)) { boolean valid = validateIntegralType( expression.getFirstExpression(), expression.parameterExpressionBNF(0), ModExpression_FirstExpression_WrongType ); updateStatus(result, 0, valid); } // Don't validate the second expression if it's not valid if (isValid(result, 1)) { boolean valid = validateIntegralType( expression.getSecondExpression(), expression.parameterExpressionBNF(1), ModExpression_SecondExpression_WrongType ); updateStatus(result, 1, valid); } return result; } /** * {@inheritDoc} */ @Override protected void validateNotExpression(NotExpression expression) { super.validateNotExpression(expression); validateBooleanType(expression.getExpression(), NotExpression_WrongType); } /** * {@inheritDoc} */ @Override protected void validateNullComparisonExpression(NullComparisonExpression expression) { super.validateNullComparisonExpression(expression); // Null comparisons over instances of embeddable class types are not supported StateFieldPathExpression pathExpression = getStateFieldPathExpression(expression.getExpression()); if (pathExpression != null) { Object type = helper.getType(pathExpression); if (helper.getEmbeddable(type) != null) { addProblem(pathExpression, NullComparisonExpression_InvalidType, pathExpression.toParsedText()); } } } /** * Determines whether the given {@link Expression} is of the correct type based on these rules: * <ul> * <li>The {@link Expression} returns a numeric value;</li> * <li>The {@link Expression}'s type is an numeric type.</li> * </ul> * * @param expression The {@link Expression} to validate * @param messageKey The key used to retrieve the localized message describing the problem * @return <code>false</code> if the given expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateNumericType(Expression expression, String messageKey) { if (isValid(expression, SimpleArithmeticExpressionBNF.ID) && !isNumericType(expression)) { addProblem(expression, messageKey); return false; } return true; } /** * {@inheritDoc} */ @Override protected boolean validateSqrtExpression(SqrtExpression expression) { boolean valid = super.validateSqrtExpression(expression); if (valid) { // The SQRT function takes a numeric argument valid = validateNumericType(expression.getExpression(), SqrtExpression_WrongType); } return valid; } /** * Determines whether the given {@link Expression} is of the correct type based on these rules: * <ul> * <li>The {@link Expression} returns a String value;</li> * <li>The {@link Expression}'s type is a String type.</li> * </ul> * * @param expression The {@link Expression} to validate * @param messageKey The key used to retrieve the localized message describing the problem * @return <code>false</code> if the given expression was validated and is invalid; * <code>true</code> otherwise */ protected boolean validateStringType(Expression expression, String messageKey) { if (isValid(expression, StringPrimaryBNF.ID) && !isStringType(expression)) { addProblem(expression, messageKey, expression.toParsedText()); return false; } return true; } /** * {@inheritDoc} */ @Override protected int validateSubstringExpression(SubstringExpression expression) { int result = super.validateSubstringExpression(expression); if (isValid(result, 0)) { boolean valid = validateStringType( expression.getFirstExpression(), SubstringExpression_FirstExpression_WrongType ); updateStatus(result, 0, valid); } // The second and third arguments of the SUBSTRING function denote the starting position and // length of the substring to be returned. These arguments are integers if (isValid(result, 1)) { boolean valid = validateIntegralType( expression.getSecondExpression(), expression.getParameterQueryBNFId(1), SubstringExpression_SecondExpression_WrongType ); updateStatus(result, 1, valid); } // The third argument is optional for JPA 2.0 if (isValid(result, 2) && getJPAVersion().isNewerThanOrEqual(JPAVersion.VERSION_2_0)) { boolean valid = validateIntegralType( expression.getThirdExpression(), expression.getParameterQueryBNFId(2), SubstringExpression_ThirdExpression_WrongType ); updateStatus(result, 2, valid); } return result; } /** * {@inheritDoc} */ @Override protected boolean validateSumFunction(SumFunction expression) { boolean valid = super.validateSumFunction(expression); if (valid) { // Arguments to the functions SUM must be numeric valid = validateNumericType(expression.getExpression(), SumFunction_WrongType); } return valid; } /** * {@inheritDoc} */ @Override @SuppressWarnings("null") protected boolean validateUpdateItem(UpdateItem expression) { // First validate the path expression boolean valid = super.validateUpdateItem(expression); if (valid) { // Retrieve the entity to make sure the state field is part of it AbstractSchemaName abstractSchemaName = findAbstractSchemaName(expression); String entityName = (abstractSchemaName != null) ? abstractSchemaName.getText() : null; if (ExpressionTools.stringIsNotEmpty(entityName)) { Object entity = helper.getEntityNamed(entityName); // Check the existence of the state field on the entity if ((entity != null) && expression.hasSpaceAfterStateFieldPathExpression()) { StateFieldPathExpression pathExpression = getStateFieldPathExpression(expression.getStateFieldPathExpression()); String stateFieldValue = (pathExpression != null) ? pathExpression.toParsedText() : null; if (ExpressionTools.stringIsNotEmpty(stateFieldValue)) { // State field without a dot if (stateFieldValue.indexOf(".") == -1) { Object mapping = helper.getMappingNamed(entity, stateFieldValue); if (mapping == null) { addProblem(pathExpression, UpdateItem_NotResolvable, stateFieldValue); } else { validateUpdateItemTypes(expression, helper.getMappingType(mapping)); } } else { Object type = helper.getType(pathExpression); if (!helper.isTypeResolvable(type)) { addProblem(pathExpression, UpdateItem_NotResolvable, stateFieldValue); } else { validateUpdateItemTypes(expression, type); } } } } } } return valid; } protected void validateUpdateItemTypes(UpdateItem expression, Object type) { if (expression.hasNewValue()) { Expression newValue = expression.getNewValue(); ITypeHelper typeHelper = getTypeHelper(); boolean nullValue = isNullValue(newValue); // A NULL value is ignored, except if the type is a primitive, null cannot be // assigned to a mapping of primitive type if (nullValue) { if (typeHelper.isPrimitiveType(type)) { addProblem(expression, UpdateItem_NullNotAssignableToPrimitive); } return; } Object newValueType = getType(newValue); // Do a quick check for known JDK types: // 1) Date/Time/Timestamp // 2) Any classes related to a number, eg long/Long etc if (!helper.isTypeResolvable(newValueType) || typeHelper.isDateType(type) && typeHelper.isDateType(newValueType) || (typeHelper.isNumericType(type) || typeHelper.isPrimitiveType(type)) && (typeHelper.isNumericType(newValueType) || typeHelper.isPrimitiveType(newValueType))) { return; } // The new value's type can't be assigned to the item's type if (!helper.isAssignableTo(newValueType, type)) { addProblem( expression, UpdateItem_NotAssignable, helper.getTypeName(newValueType), helper.getTypeName(type) ); } } } /** * {@inheritDoc} */ @Override protected boolean validateUpperExpression(UpperExpression expression) { boolean valid = super.validateUpperExpression(expression); if (valid) { // The UPPER function convert a string to upper case, // with regard to the locale of the database valid = validateStringType(expression.getExpression(), UpperExpression_WrongType); } return valid; } /** * {@inheritDoc} */ @Override protected void validateValueExpression(ValueExpression expression) { validateMapIdentificationVariable(expression); super.validateValueExpression(expression); } /** * This visitor validates expression that is a boolean literal to make sure the type is a * <b>Boolean</b>. */ protected class BooleanTypeValidator extends TypeValidator { /** * {@inheritDoc} */ @Override protected boolean isRightType(Object type) { return getTypeHelper().isBooleanType(type); } /** * {@inheritDoc} */ @Override public void visit(AllOrAnyExpression expression) { // ALL|ANY|SOME always returns a boolean value valid = true; } /** * {@inheritDoc} */ @Override public void visit(AndExpression expression) { // AND always returns a boolean value valid = true; } /** * {@inheritDoc} */ @Override public void visit(BetweenExpression expression) { // BETWEEN always returns a boolean value valid = true; } /** * {@inheritDoc} */ @Override public void visit(ComparisonExpression expression) { // A comparison always returns a boolean value valid = true; } /** * {@inheritDoc} */ @Override public void visit(EmptyCollectionComparisonExpression expression) { // IS EMPTY always returns a boolean value valid = true; } /** * {@inheritDoc} */ @Override public void visit(ExistsExpression expression) { // EXISTS always returns a boolean value valid = true; } /** * {@inheritDoc} */ @Override public void visit(KeywordExpression expression) { valid = true; } /** * {@inheritDoc} */ @Override public void visit(LikeExpression expression) { // LIKE always returns a boolean value valid = true; } /** * {@inheritDoc} */ @Override public void visit(NotExpression expression) { // NOT always returns a boolean value valid = true; } /** * {@inheritDoc} */ @Override public void visit(NullComparisonExpression expression) { // IS NULL always returns a boolean value valid = true; } /** * {@inheritDoc} */ @Override public void visit(OrExpression expression) { // OR always returns a boolean value valid = true; } } protected static class NullValueVisitor extends AbstractExpressionVisitor { /** * Determines whether the {@link Expression} visited represents {@link Expression#NULL}. */ protected boolean valid; /** * {@inheritDoc} */ @Override public void visit(KeywordExpression expression) { valid = expression.getText() == Expression.NULL; } } /** * This visitor validates expression that is a numeric literal to make sure the type is an * instance of <b>Number</b>. */ protected class NumericTypeValidator extends TypeValidator { /** * {@inheritDoc} */ @Override protected boolean isRightType(Object type) { return getTypeHelper().isNumericType(type); } /** * {@inheritDoc} */ @Override public void visit(AbsExpression expression) { // ABS always returns a numeric value valid = true; } /** * {@inheritDoc} */ @Override public void visit(AdditionExpression expression) { // An addition expression always returns a numeric value valid = true; } /** * {@inheritDoc} */ @Override public void visit(ArithmeticFactor expression) { // +/- is always numeric value valid = true; } /** * {@inheritDoc} */ @Override public void visit(AvgFunction expression) { // AVG always returns a double valid = true; } /** * {@inheritDoc} */ @Override public void visit(CountFunction expression) { // COUNT always returns a long valid = true; } /** * {@inheritDoc} */ @Override public void visit(DivisionExpression expression) { // A division expression always returns a numeric value valid = true; } /** * {@inheritDoc} */ @Override public void visit(IndexExpression expression) { // INDEX always returns an integer valid = true; } /** * {@inheritDoc} */ @Override public void visit(LengthExpression expression) { // LENGTH always returns an integer valid = true; } /** * {@inheritDoc} */ @Override public void visit(LocateExpression expression) { // LOCATE always returns an integer valid = true; } /** * {@inheritDoc} */ @Override public void visit(MaxFunction expression) { // SUM always returns a numeric value valid = true; } /** * {@inheritDoc} */ @Override public void visit(MinFunction expression) { // SUM always returns a numeric value valid = true; } /** * {@inheritDoc} */ @Override public void visit(ModExpression expression) { // MOD always returns an integer valid = true; } /** * {@inheritDoc} */ @Override public void visit(MultiplicationExpression expression) { // A multiplication expression always returns a numeric value valid = true; } /** * {@inheritDoc} */ @Override public void visit(NumericLiteral expression) { // A numeric literal is by definition valid valid = true; } /** * {@inheritDoc} */ @Override public void visit(SizeExpression expression) { // SIZE always returns an integer valid = true; } /** * {@inheritDoc} */ @Override public void visit(SqrtExpression expression) { // SQRT always returns a double valid = true; } /** * {@inheritDoc} */ @Override public void visit(SubtractionExpression expression) { // A subtraction expression always returns a numeric value valid = true; } /** * {@inheritDoc} */ @Override public void visit(SumFunction expression) { // SUM always returns a long valid = true; } } protected static class ResultVariableInOrderByVisitor extends AbstractExpressionVisitor { public boolean result; /** * {@inheritDoc} */ @Override public void visit(IdentificationVariable expression) { expression.getParent().accept(this); } /** * {@inheritDoc} */ @Override public void visit(OrderByClause expression) { result = true; } /** * {@inheritDoc} */ @Override public void visit(OrderByItem expression) { expression.getParent().accept(this); } } /** * This visitor validates that the {@link Expression} is a string primary and to make sure the * type is String. */ protected class StringTypeValidator extends TypeValidator { /** * {@inheritDoc} */ @Override protected boolean isRightType(Object type) { return getTypeHelper().isStringType(type); } /** * {@inheritDoc} */ @Override public void visit(ConcatExpression expression) { // CONCAT always returns a string valid = true; } /** * {@inheritDoc} */ @Override public void visit(LowerExpression expression) { // LOWER always returns a string valid = true; } /** * {@inheritDoc} */ @Override public void visit(StringLiteral expression) { // A string literal is by definition valid valid = true; } /** * {@inheritDoc} */ @Override public void visit(SubstringExpression expression) { // SUBSTRING always returns a string valid = true; } /** * {@inheritDoc} */ @Override public void visit(TrimExpression expression) { // TRIM always returns a string valid = true; } /** * {@inheritDoc} */ @Override public void visit(UpperExpression expression) { // UPPER always returns a string valid = true; } } /** * The basic validator for validating the type of an {@link Expression}. */ protected abstract class TypeValidator extends AbstractExpressionVisitor { /** * Determines whether the expression that was visited returns a number. */ protected boolean valid; /** * Determines whether the given type is the expected type. * * @param type The type to validate * @return <code>true</code> if the given type is of the expected type; <code>false</code> if * it's not the right type */ protected abstract boolean isRightType(Object type); /** * {@inheritDoc} */ @Override public final void visit(CaseExpression expression) { Object type = getType(expression); valid = isRightType(type); } /** * {@inheritDoc} */ @Override public final void visit(CoalesceExpression expression) { Object type = getType(expression); valid = isRightType(type); } /** * {@inheritDoc} */ @Override public final void visit(InputParameter expression) { // An input parameter can't be validated until the query // is executed so it is assumed to be valid valid = true; } /** * {@inheritDoc} */ @Override public void visit(NullExpression expression) { // The missing expression is validated by GrammarValidator valid = true; } /** * {@inheritDoc} */ @Override public final void visit(NullIfExpression expression) { expression.getFirstExpression().accept(this); } /** * {@inheritDoc} */ @Override public final void visit(StateFieldPathExpression expression) { Object type = getType(expression); valid = isRightType(type); } /** * {@inheritDoc} */ @Override public final void visit(SubExpression expression) { expression.getExpression().accept(this); } } protected static class UpdateClauseAbstractSchemaNameFinder extends AbstractExpressionVisitor { protected AbstractSchemaName expression; /** * {@inheritDoc} */ @Override public void visit(AbstractSchemaName expression) { this.expression = expression; } /** * {@inheritDoc} */ @Override public void visit(CollectionExpression expression) { expression.getParent().accept(this); } /** * {@inheritDoc} */ @Override public void visit(RangeVariableDeclaration expression) { expression.getRootObject().accept(this); } /** * {@inheritDoc} */ @Override public void visit(UpdateClause expression) { expression.getRangeVariableDeclaration().accept(this); } /** * {@inheritDoc} */ @Override public void visit(UpdateItem expression) { expression.getParent().accept(this); } } }