/******************************************************************************* * 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 org.eclipse.persistence.jpa.jpql.AbstractEclipseLinkSemanticValidator.EclipseLinkOwningClauseVisitor; import org.eclipse.persistence.jpa.jpql.parser.AbstractEclipseLinkExpressionVisitor; import org.eclipse.persistence.jpa.jpql.parser.AbstractSelectClause; import org.eclipse.persistence.jpa.jpql.parser.AsOfClause; import org.eclipse.persistence.jpa.jpql.parser.CastExpression; import org.eclipse.persistence.jpa.jpql.parser.ConnectByClause; import org.eclipse.persistence.jpa.jpql.parser.DatabaseType; import org.eclipse.persistence.jpa.jpql.parser.DatabaseTypeFactory; import org.eclipse.persistence.jpa.jpql.parser.DefaultEclipseLinkJPQLGrammar; import org.eclipse.persistence.jpa.jpql.parser.EclipseLinkExpressionVisitor; import org.eclipse.persistence.jpa.jpql.parser.Expression; import org.eclipse.persistence.jpa.jpql.parser.ExtractExpression; import org.eclipse.persistence.jpa.jpql.parser.HierarchicalQueryClause; import org.eclipse.persistence.jpa.jpql.parser.InExpression; import org.eclipse.persistence.jpa.jpql.parser.InputParameter; import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar; import org.eclipse.persistence.jpa.jpql.parser.OrderSiblingsByClause; import org.eclipse.persistence.jpa.jpql.parser.PatternValueBNF; import org.eclipse.persistence.jpa.jpql.parser.RegexpExpression; import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectClause; import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement; import org.eclipse.persistence.jpa.jpql.parser.StartWithClause; import org.eclipse.persistence.jpa.jpql.parser.StringExpressionBNF; import org.eclipse.persistence.jpa.jpql.parser.TableExpression; import org.eclipse.persistence.jpa.jpql.parser.TableVariableDeclaration; import org.eclipse.persistence.jpa.jpql.parser.UnionClause; import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.*; import static org.eclipse.persistence.jpa.jpql.parser.Expression.*; /** * This validator adds EclipseLink extension over what the JPA functional specification had defined. * <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. * * @version 2.5 * @since 2.4 * @author Pascal Filion */ public class EclipseLinkGrammarValidator extends AbstractGrammarValidator implements EclipseLinkExpressionVisitor { private InExpressionVisitor inExpressionVisitor; private InExpressionWithNestedArrayVisitor inExpressionWithNestedArrayVisitor; /** * Creates a new <code>EclipseLinkGrammarValidator</code>. * * @param jpqlGrammar The {@link JPQLGrammar} that defines how the JPQL query was parsed */ public EclipseLinkGrammarValidator(JPQLGrammar jpqlGrammar) { super(jpqlGrammar); } protected AbstractSingleEncapsulatedExpressionHelper<CastExpression> buildCastExpressionHelper() { return new AbstractSingleEncapsulatedExpressionHelper<CastExpression>(this) { @Override protected String encapsulatedExpressionInvalidKey(CastExpression expression) { return CastExpression_InvalidExpression; } @Override protected String encapsulatedExpressionMissingKey(CastExpression expression) { return CastExpression_MissingExpression; } public String leftParenthesisMissingKey(CastExpression expression) { return CastExpression_MissingLeftParenthesis; } public String rightParenthesisMissingKey(CastExpression expression) { return CastExpression_MissingRightParenthesis; } }; } protected AbstractDoubleEncapsulatedExpressionHelper<DatabaseType> buildDatabaseTypeHelper() { return new AbstractDoubleEncapsulatedExpressionHelper<DatabaseType>(this) { @Override protected String firstExpressionInvalidKey() { return DatabaseType_InvalidFirstExpression; } @Override protected String firstExpressionMissingKey() { return DatabaseType_MissingFirstExpression; } @Override protected boolean hasComma(DatabaseType expression) { // If the second expression is not specified, then the comma is not needed return expression.hasComma() || !expression.hasSecondExpression(); } @Override protected boolean hasFirstExpression(DatabaseType expression) { return !expression.hasLeftParenthesis() || expression.hasFirstExpression(); } @Override public boolean hasLeftParenthesis(DatabaseType expression) { if (expression.hasLeftParenthesis()) { return true; } // The parenthesis are optional unless one the following // items is specified, then '(' is required return !(expression.hasFirstExpression() || expression.hasComma() || expression.hasSecondExpression() || expression.hasRightParenthesis()); } @Override public boolean hasRightParenthesis(DatabaseType expression) { if (expression.hasRightParenthesis()) { return true; } // The parenthesis are optional unless one the following // items is specified, then ')' is required return !(expression.hasLeftParenthesis() || expression.hasFirstExpression() || expression.hasComma() || expression.hasSecondExpression()); } @Override protected boolean hasSecondExpression(DatabaseType expression) { return !expression.hasComma() || expression.hasSecondExpression(); } public String leftParenthesisMissingKey(DatabaseType expression) { return DatabaseType_MissingLeftParenthesis; } @Override protected String missingCommaKey() { return DatabaseType_MissingComma; } public String rightParenthesisMissingKey(DatabaseType expression) { return DatabaseType_MissingRightParenthesis; } @Override protected String secondExpressionInvalidKey() { return DatabaseType_InvalidSecondExpression; } @Override protected String secondExpressionMissingKey() { return DatabaseType_MissingSecondExpression; } }; } protected AbstractSingleEncapsulatedExpressionHelper<ExtractExpression> buildExtractExpressionHelper() { return new AbstractSingleEncapsulatedExpressionHelper<ExtractExpression>(this) { @Override protected String encapsulatedExpressionInvalidKey(ExtractExpression expression) { return ExtractExpression_InvalidExpression; } @Override protected String encapsulatedExpressionMissingKey(ExtractExpression expression) { return ExtractExpression_MissingExpression; } public String leftParenthesisMissingKey(ExtractExpression expression) { return ExtractExpression_MissingLeftParenthesis; } @Override protected int lengthBeforeEncapsulatedExpression(ExtractExpression expression) { return expression.getDatePart().length() + (expression.hasSpaceAfterDatePart() ? 1 : 0) + (expression.hasFrom() ? 4 /* FROM */ : 0) + (expression.hasSpaceAfterFrom() ? 1 : 0); } public String rightParenthesisMissingKey(ExtractExpression expression) { return ExtractExpression_MissingRightParenthesis; } }; } protected InExpressionVisitor buildInExpressionVisitor() { return new InExpressionVisitor(); } protected InExpressionWithNestedArrayVisitor buildInExpressionWithNestedArrayVisitor() { return new InExpressionWithNestedArrayVisitor(this); } /** * {@inheritDoc} */ @Override protected LiteralVisitor buildLiteralVisitor() { return new EclipseLinkLiteralVisitor(); } /** * {@inheritDoc} */ @Override protected OwningClauseVisitor buildOwningClauseVisitor() { return new EclipseLinkOwningClauseVisitor(); } protected AbstractSingleEncapsulatedExpressionHelper<TableExpression> buildTableExpressionHelper() { return new AbstractSingleEncapsulatedExpressionHelper<TableExpression>(this) { @Override protected String encapsulatedExpressionInvalidKey(TableExpression expression) { return TableExpression_InvalidExpression; } @Override protected String encapsulatedExpressionMissingKey(TableExpression expression) { return TableExpression_MissingExpression; } public String leftParenthesisMissingKey(TableExpression expression) { return TableExpression_MissingLeftParenthesis; } public String rightParenthesisMissingKey(TableExpression expression) { return TableExpression_MissingRightParenthesis; } }; } protected AbstractSingleEncapsulatedExpressionHelper<CastExpression> castExpressionHelper() { AbstractSingleEncapsulatedExpressionHelper<CastExpression> helper = getHelper(CAST); if (helper == null) { helper = buildCastExpressionHelper(); registerHelper(CAST, helper); } return helper; } protected AbstractDoubleEncapsulatedExpressionHelper<DatabaseType> databaseTypeHelper() { AbstractDoubleEncapsulatedExpressionHelper<DatabaseType> helper = getHelper(DatabaseTypeFactory.ID); if (helper == null) { helper = buildDatabaseTypeHelper(); registerHelper(DatabaseTypeFactory.ID, helper); } return helper; } protected AbstractSingleEncapsulatedExpressionHelper<ExtractExpression> extractExpressionHelper() { AbstractSingleEncapsulatedExpressionHelper<ExtractExpression> helper = getHelper(EXTRACT); if (helper == null) { helper = buildExtractExpressionHelper(); registerHelper(EXTRACT, helper); } return helper; } protected InExpressionVisitor getInExpressionVisitor() { if (inExpressionVisitor == null) { inExpressionVisitor = buildInExpressionVisitor(); } return inExpressionVisitor; } protected InExpressionWithNestedArrayVisitor getInExpressionWithNestedArray() { if (inExpressionWithNestedArrayVisitor == null) { inExpressionWithNestedArrayVisitor = buildInExpressionWithNestedArrayVisitor(); } return inExpressionWithNestedArrayVisitor; } /** * {@inheritDoc} */ @Override protected EclipseLinkOwningClauseVisitor getOwningClauseVisitor() { return (EclipseLinkOwningClauseVisitor) super.getOwningClauseVisitor(); } /** * Determines whether the persistence provider is EclipseLink or not. * * @return <code>true</code> if the persistence provider is EclipseLink; <code>false</code> otherwise */ protected final boolean isEclipseLink() { return DefaultEclipseLinkJPQLGrammar.PROVIDER_NAME.equals(getProvider()); } /** * Determines whether the subquery is part of an <code><b>IN</b></code> expression where the * left expression is a nested array. * * @param expression The {@link SimpleSelectClause} of the subquery * @return <code>true</code> if the subquery is in an <code><b>IN</b></code> expression and its * left expression is a nested array */ protected boolean isInExpressionWithNestedArray(SimpleSelectClause expression) { InExpressionWithNestedArrayVisitor visitor = getInExpressionWithNestedArray(); try { expression.accept(visitor); return visitor.valid; } finally { visitor.valid = false; } } /** * {@inheritDoc} */ @Override protected boolean isInputParameterInValidLocation(InputParameter expression) { return true; } /** * {@inheritDoc} */ @Override protected boolean isJoinFetchIdentifiable() { EclipseLinkVersion version = EclipseLinkVersion.value(getGrammar().getProviderVersion()); return version.isNewerThanOrEqual(EclipseLinkVersion.VERSION_2_4); } /** * {@inheritDoc} */ @Override protected boolean isMultipleSubquerySelectItemsAllowed(SimpleSelectClause expression) { return isInExpressionWithNestedArray(expression); } protected boolean isOwnedByInExpression(Expression expression) { InExpressionVisitor visitor = getInExpressionVisitor(); expression.accept(visitor); return visitor.expression != null; } /** * Determines whether the given {@link Expression} is a child of the <b>UNION</b> clause. * * @param expression The {@link Expression} to visit its parent hierarchy up to the clause * @return <code>true</code> if the first parent being a clause is the <b>UNION</b> clause; * <code>false</code> otherwise */ protected boolean isOwnedByUnionClause(Expression expression) { EclipseLinkOwningClauseVisitor visitor = getOwningClauseVisitor(); try { expression.accept(visitor); return visitor.unionClause != null; } finally { visitor.dispose(); } } /** * {@inheritDoc} */ @Override protected boolean isSubqueryAllowedAnywhere() { EclipseLinkVersion version = EclipseLinkVersion.value(getProviderVersion()); return version.isNewerThanOrEqual(EclipseLinkVersion.VERSION_2_4); } protected AbstractSingleEncapsulatedExpressionHelper<TableExpression> tableExpressionHelper() { AbstractSingleEncapsulatedExpressionHelper<TableExpression> helper = getHelper(TABLE); if (helper == null) { helper = buildTableExpressionHelper(); registerHelper(TABLE, helper); } return helper; } /** * {@inheritDoc} */ @Override protected void validateAbstractSelectClause(AbstractSelectClause expression, boolean multipleSelectItemsAllowed) { // A subquery can have multiple select items if it is // - used as a "root" object in the top-level FROM clause // - defined in a UNION clause // - used in an IN expression // If the flag is false, then the SELECT clause is from a subquery if (!multipleSelectItemsAllowed) { Expression parent = expression.getParent(); multipleSelectItemsAllowed = isOwnedByFromClause (parent) || isOwnedByUnionClause (parent) || isOwnedByInExpression(parent); } super.validateAbstractSelectClause(expression, multipleSelectItemsAllowed); } /** * {@inheritDoc} */ public void visit(AsOfClause expression) { } /** * {@inheritDoc} */ public void visit(CastExpression expression) { // Wrong JPA version if (!isEclipseLink()) { addProblem(expression, CastExpression_InvalidJPAVersion); } else { validateAbstractSingleEncapsulatedExpression(expression, castExpressionHelper()); // Database type if (expression.hasExpression() || expression.hasAs()) { // Missing database type if (!expression.hasDatabaseType()) { int startPosition = position(expression) + 4 /* CAST */ + (expression.hasLeftParenthesis() ? 1 : 0) + length(expression.getExpression()) + (expression.hasSpaceAfterExpression() ? 1 : 0) + (expression.hasAs() ? 2 : 0) + (expression.hasSpaceAfterAs() ? 1 : 0); addProblem(expression, startPosition, CastExpression_MissingDatabaseType); } // Validate database type else { expression.getDatabaseType().accept(this); } } } } /** * {@inheritDoc} */ public void visit(ConnectByClause expression) { // TODO: 2.5 } /** * {@inheritDoc} */ public void visit(DatabaseType expression) { validateAbstractDoubleEncapsulatedExpression(expression, databaseTypeHelper()); } /** * {@inheritDoc} */ public void visit(ExtractExpression expression) { // Wrong JPA version if (!isEclipseLink()) { addProblem(expression, ExtractExpression_InvalidJPAVersion); } else { validateAbstractSingleEncapsulatedExpression(expression, extractExpressionHelper()); // Missing date part if (expression.hasLeftParenthesis() && !expression.hasDatePart()) { int startPosition = position(expression) + 7 /* EXTRACT */ + (expression.hasLeftParenthesis() ? 1 : 0); addProblem(expression, startPosition, ExtractExpression_MissingDatePart); } } } /** * {@inheritDoc} */ public void visit(HierarchicalQueryClause expression) { // TODO: 2.5 } /** * {@inheritDoc} */ public void visit(OrderSiblingsByClause expression) { // TODO } /** * {@inheritDoc} */ public void visit(RegexpExpression expression) { // Wrong JPA version if (!isEclipseLink()) { addProblem(expression, RegexpExpression_InvalidJPAVersion); } else { // Missing string expression if (!expression.hasStringExpression()) { int startPosition = position(expression); int endPosition = startPosition; addProblem( expression, startPosition, endPosition, RegexpExpression_MissingStringExpression ); } else { Expression stringExpression = expression.getStringExpression(); // Invalid string expression if (!isValid(stringExpression, StringExpressionBNF.ID)) { int startPosition = position(stringExpression); int endPosition = startPosition + length(stringExpression); addProblem( expression, startPosition, endPosition, RegexpExpression_InvalidStringExpression ); } // Validate string expression else { stringExpression.accept(this); } } // Missing pattern value if (!expression.hasPatternValue()) { int startPosition = position(expression) + length(expression.getStringExpression()) + (expression.hasSpaceAfterStringExpression() ? 1 : 0) + 6 /* REGEXP */ + (expression.hasSpaceAfterIdentifier() ? 1 : 0); int endPosition = startPosition; addProblem(expression, startPosition, endPosition, RegexpExpression_MissingPatternValue); } else { Expression patternValue = expression.getStringExpression(); // Invalid string expression if (!isValid(patternValue, PatternValueBNF.ID)) { int startPosition = position(expression) + length(expression.getStringExpression()) + (expression.hasSpaceAfterStringExpression() ? 1 : 0) + 6 /* REGEXP */ + (expression.hasSpaceAfterIdentifier() ? 1 : 0); int endPosition = startPosition + length(patternValue); addProblem( expression, startPosition, endPosition, RegexpExpression_InvalidPatternValue ); } // Validate pattern value else { patternValue.accept(this); } } } } /** * {@inheritDoc} */ public void visit(StartWithClause expression) { // TODO: 2.5 } /** * {@inheritDoc} */ public void visit(TableExpression expression) { validateAbstractSingleEncapsulatedExpression(expression, tableExpressionHelper()); } /** * {@inheritDoc} */ public void visit(TableVariableDeclaration expression) { // Wrong JPA version if (!isEclipseLink()) { addProblem(expression, TableVariableDeclaration_InvalidJPAVersion); } else { TableExpression tableExpression = expression.getTableExpression(); // Validate the table expression tableExpression.accept(this); // The identification variable is missing if (!expression.hasIdentificationVariable()) { int startPosition = position(expression) + length(tableExpression) + (expression.hasSpaceAfterTableExpression() ? 1 : 0) + (expression.hasAs() ? 2 : 0) + (expression.hasSpaceAfterAs() ? 1 : 0); addProblem(expression, startPosition, TableVariableDeclaration_MissingIdentificationVariable); } // Validate the identification variable else { expression.getIdentificationVariable().accept(this); } } } /** * {@inheritDoc} */ public void visit(UnionClause expression) { // Wrong JPA version if (!isEclipseLink()) { addProblem(expression, UnionClause_InvalidJPAVersion); } // Missing subquery else if (!expression.hasQuery()) { int startPosition = position(expression) + expression.getIdentifier().length() + (expression.hasSpaceAfterIdentifier() ? 1 : 0) + (expression.hasAll() ? 3 : 0) + (expression.hasSpaceAfterAll() ? 1 : 0); addProblem(expression, startPosition, UnionClause_MissingExpression); } // Validate the subquery else { expression.getQuery().accept(this); } } // Made static for performance reasons. protected static class InExpressionVisitor extends AbstractEclipseLinkExpressionVisitor { protected InExpression expression; @Override public void visit(InExpression expression) { this.expression = expression; } } // Made static final for performance reasons. protected static final class InExpressionWithNestedArrayVisitor extends AbstractEclipseLinkExpressionVisitor { private final EclipseLinkGrammarValidator visitor; protected InExpressionWithNestedArrayVisitor(EclipseLinkGrammarValidator visitor) { this.visitor = visitor; } /** * Determines whether the left expression of an <code><b>IN</b></code> expression is a nested * array when the <code><b>IN</b></code> item is a subquery. */ public boolean valid; /** * {@inheritDoc} */ @Override public void visit(InExpression expression) { valid = visitor.isNestedArray(expression.getExpression()); } /** * {@inheritDoc} */ @Override public void visit(SimpleSelectClause expression) { expression.getParent().accept(this); } /** * {@inheritDoc} */ @Override public void visit(SimpleSelectStatement expression) { expression.getParent().accept(this); } } }