Package org.eclipse.persistence.jpa.jpql

Source Code of org.eclipse.persistence.jpa.jpql.AbstractEclipseLinkSemanticValidator$TableExpressionVisitor

/*******************************************************************************
* Copyright (c) 2006, 2013 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
*     Oracle - initial API and implementation
*
******************************************************************************/
package org.eclipse.persistence.jpa.jpql;

import org.eclipse.persistence.jpa.jpql.parser.AbstractEclipseLinkExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AnonymousExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AsOfClause;
import org.eclipse.persistence.jpa.jpql.parser.BadExpression;
import org.eclipse.persistence.jpa.jpql.parser.CastExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConnectByClause;
import org.eclipse.persistence.jpa.jpql.parser.DatabaseType;
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.FunctionExpression;
import org.eclipse.persistence.jpa.jpql.parser.HierarchicalQueryClause;
import org.eclipse.persistence.jpa.jpql.parser.InExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullExpression;
import org.eclipse.persistence.jpa.jpql.parser.OrderSiblingsByClause;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
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.StateFieldPathExpression;
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.*;

/**
* This validator is responsible to gather the problems found in a JPQL query by validating the
* content to make sure it is semantically valid for EclipseLink. 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>
* <b>Note:</b> EclipseLink does not validate types, but leaves it to the database. This is because
* some databases such as Oracle allow different types to different functions and perform implicit
* type conversion. i.e. <code>CONCAT('test', 2)</code> returns <code>'test2'</code>. Also the
* <b>FUNC</b> function has an unknown type, so should be allowed with any function.
* <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 EclipseLinkGrammarValidator
*
* @version 2.5.1
* @since 2.4
* @author Pascal Filion
*/
public class AbstractEclipseLinkSemanticValidator extends AbstractSemanticValidator
                                                  implements EclipseLinkExpressionVisitor {

  /**
   * The following extension can be used to give access to non-JPA metadata artifacts, such as
   * database tables and columns.
   */
  private EclipseLinkSemanticValidatorExtension extension;

  /**
   * This visitor calculates the number of items the subquery <code><b>SELECT</b></code> clause has.
   */
  private SubquerySelectItemCalculator subquerySelectItemCalculator;

  /**
   * This visitor determines if the {@link Expression} being visited is a {@link TableExpression}.
   */
  private TableExpressionVisitor tableExpressionVisitor;

  /**
   * Creates a new <code>AbstractEclipseLinkSemanticValidator</code>.
   *
   * @param helper The given helper allows this validator to access the JPA artifacts without using
   * Hermes SPI
   * @param extension The following extension can be used to give access to non-JPA metadata
   * artifacts, such as database tables and columns
   * @exception NullPointerException The given {@link SemanticValidatorHelper} cannot be <code>null</code>
   */
  protected AbstractEclipseLinkSemanticValidator(SemanticValidatorHelper helper,
                                                 EclipseLinkSemanticValidatorExtension extension) {

    super(helper);
    this.extension = extension;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected LiteralVisitor buildLiteralVisitor() {
    return new EclipseLinkLiteralVisitor();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected OwningClauseVisitor buildOwningClauseVisitor() {
    return new EclipseLinkOwningClauseVisitor();
  }

  protected SubquerySelectItemCalculator buildSubquerySelectItemCalculator() {
    return new SubquerySelectItemCalculator();
  }

  protected TableExpressionVisitor buildTableExpressionVisitor() {
    return new TableExpressionVisitor();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected TopLevelFirstDeclarationVisitor buildTopLevelFirstDeclarationVisitor() {
    return new TopLevelFirstDeclarationVisitor();
  }

  protected JPQLQueryDeclaration getDeclaration(String variableName) {

    for (JPQLQueryDeclaration declaration : helper.getAllDeclarations()) {

      if (declaration.getVariableName().equalsIgnoreCase(variableName)) {
        return declaration;
      }
    }

    return null;
  }

  /**
   * Returns the extension that gives access to non-JPA metadata artifacts, such as database tables
   * and columns.
   *
   * @return An extension giving access to non-JPA metadata artifacts or {@link
   * EclipseLinkSemanticValidatorExtension#NULL_EXTENSION} if no extension was provided
   */
  protected EclipseLinkSemanticValidatorExtension getExtension() {
    return extension;
  }

  protected SubquerySelectItemCalculator getSubquerySelectItemCalculator() {
    if (subquerySelectItemCalculator == null) {
      subquerySelectItemCalculator = buildSubquerySelectItemCalculator();
    }
    return subquerySelectItemCalculator;
  }

  protected TableExpressionVisitor getTableExpressionVisitor() {
    if (tableExpressionVisitor == null) {
      tableExpressionVisitor = buildTableExpressionVisitor();
    }
    return tableExpressionVisitor;
  }

  protected boolean isTableExpression(Expression expression) {
    TableExpressionVisitor visitor = getTableExpressionVisitor();
    try {
      visitor.expression = expression;
      expression.accept(visitor);
      return visitor.valid;
    }
    finally {
      visitor.valid = false;
      visitor.expression = null;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected PathType selectClausePathExpressionPathType() {
    return PathType.ANY_FIELD_INCLUDING_COLLECTION;
  }

  /**
   * Retrieves the number of select items the given subquery has.
   *
   * @param subquery The {@link Expression} to visit, which should represents a subquery
   * @return The number of select items or 0 if the subquery is malformed or incomplete
   */
  protected int subquerySelectItemCount(Expression subquery) {
    SubquerySelectItemCalculator visitor = getSubquerySelectItemCalculator();
    try {
      visitor.count = 0;
      subquery.accept(visitor);
      return visitor.count;
    }
    finally {
      visitor.count = 0;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void validateFunctionExpression(FunctionExpression expression) {
    super.validateFunctionExpression(expression);

    // No need to validate if no extension was provided
    // Validate the column name
    if (extension != EclipseLinkSemanticValidatorExtension.NULL_EXTENSION &&
       (expression.getIdentifier() == Expression.COLUMN) &&
        expression.hasExpression()) {

      String columnName = expression.getUnquotedFunctionName();
      String variableName = literal(expression.getExpression(), LiteralType.IDENTIFICATION_VARIABLE);

      if (ExpressionTools.stringIsNotEmpty(variableName) &&
          ExpressionTools.stringIsNotEmpty(columnName)) {

        // Retrieve the declaration associated with the identification variable
        JPQLQueryDeclaration declaration = getDeclaration(variableName);

        // Only a range variable declaration can be used
        if (declaration.getType().isRange()) {
          String entityName = literal(declaration.getBaseExpression(), LiteralType.ABSTRACT_SCHEMA_NAME);

          if (entityName != ExpressionTools.EMPTY_STRING) {

            // Retrieve the primary table of the entity
            String tableName = extension.getEntityTable(entityName);

            // The column is not on the primary table
            if (ExpressionTools.stringIsNotEmpty(tableName) &&
                !extension.columnExists(tableName, expression.getUnquotedFunctionName())) {

              int startPosition = position(expression) +
                                  Expression.COLUMN.length() +
                                  (expression.hasLeftParenthesis() ? 1 : 0);

              int endPosition = startPosition +
                                expression.getFunctionName().length();

              addProblem(
                expression,
                startPosition,
                endPosition,
                FunctionExpression_UnknownColumn,
                columnName,
                tableName
              );
            }
          }
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void validateInExpression(InExpression expression) {
    super.validateInExpression(expression);

    // Make sure the number of items matches if the left expression
    // is a nested array and the IN items expression is a subquery
    Expression inItems = expression.getInItems();

    if (isSubquery(inItems)) {
      int nestedArraySize = nestedArraySize(expression.getExpression());
      int subquerySelectItemsSize = subquerySelectItemCount(inItems);

      if ((nestedArraySize  > -1) && (subquerySelectItemsSize != nestedArraySize) ||
          (nestedArraySize == -1) && (subquerySelectItemsSize  > 1)) {

        addProblem(expression, InExpression_InvalidItemCount);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void validateRangeVariableDeclarationRootObject(RangeVariableDeclaration expression) {

    Expression rootObject = expression.getRootObject();

    // Special case, the path expression could be a fully qualified class name,
    // make sure to not validate it as collection-valued path expression
    CollectionValuedPathExpression pathExpression = getCollectionValuedPathExpression(rootObject);

    if (pathExpression != null) {
      String path = pathExpression.toActualText();

      // The path expression is not a fully qualified class name
      if (helper.getType(path) == null) {
        pathExpression.accept(this);
      }
    }
    else {
      rootObject.accept(this);
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected PathType validPathExpressionTypeForCountFunction() {
    return PathType.ANY_FIELD_INCLUDING_COLLECTION;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected PathType validPathExpressionTypeForInExpression() {
    // Loosen up the JPA spec restriction because ANTLR parser used to allow it
    return PathType.ANY_FIELD_INCLUDING_COLLECTION;
  }

  protected PathType validPathExpressionTypeForInItem() {
    return PathType.ANY_FIELD_INCLUDING_COLLECTION;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected Boolean validateThirdPartyStateFieldPathExpression(StateFieldPathExpression expression) {

    Boolean valid = null;

    // Retrieve the identification variable
    String variableName = literal(expression.getIdentificationVariable(), LiteralType.IDENTIFICATION_VARIABLE);

    if (variableName != ExpressionTools.EMPTY_STRING) {

      // Now find the associated declaration
      JPQLQueryDeclaration declaration = getDeclaration(variableName);

      if (declaration != null) {

        // The path expression is composed with an identification variable mapping a subquery
        if (declaration.getType() == JPQLQueryDeclaration.Type.SUBQUERY) {
          valid = Boolean.TRUE;
        }
        else {
          Expression baseExpression = declaration.getBaseExpression();

          // If the base expression is a TableExpression, then we can
          // continue to resolve the table and column names
          if ((baseExpression != null) && isTableExpression(baseExpression)) {

            // No need to validate if no extension was provided,
            // but mark valid to true so it's not validated otherwise
            if (extension == EclipseLinkSemanticValidatorExtension.NULL_EXTENSION) {
              valid = Boolean.TRUE;
            }
            else {
              valid = Boolean.FALSE;

              // Retrieve the table name
              String tableName = literal(baseExpression, LiteralType.STRING_LITERAL);

              if (tableName != ExpressionTools.EMPTY_STRING) {
                tableName = ExpressionTools.unquote(tableName);

                // If the table name can be resolved, then validate the column name
                if ((tableName != null) && extension.tableExists(tableName)) {
                  String columnName = expression.getPath(1);

                  // The column cannot be found on the table
                  if (!extension.columnExists(tableName, columnName)) {
                    addProblem(
                      expression,
                      StateFieldPathExpression_UnknownColumn,
                      columnName,
                      tableName
                    );
                  }
                  else {
                    valid = Boolean.TRUE;
                  }
                }
              }
            }
          }
        }
      }
    }

    return valid;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected PathType validPathExpressionTypeForStringExpression() {
    // Loosen up the JPA spec restriction because ANTLR parser used to allow it
    return PathType.ANY_FIELD_INCLUDING_COLLECTION;
  }

  /**
   * {@inheritDoc}
   */
  public void visit(AsOfClause expression) {
    super.visit(expression);
    // Nothing to validate semantically
  }

  /**
   * {@inheritDoc}
   */
  public void visit(CastExpression expression) {
    super.visit(expression);
    // Nothing to validate semantically
  }

  /**
   * {@inheritDoc}
   */
  public void visit(ConnectByClause expression) {
    super.visit(expression);
    // Nothing to validate semantically
  }

  /**
   * {@inheritDoc}
   */
  public void visit(DatabaseType expression) {
    super.visit(expression);
    // Nothing to validate semantically
  }

  /**
   * {@inheritDoc}
   */
  public void visit(ExtractExpression expression) {
    super.visit(expression);
    // Nothing to validate semantically
  }

  /**
   * {@inheritDoc}
   */
  public void visit(HierarchicalQueryClause expression) {
    super.visit(expression);
    // Nothing to validate semantically
  }

  /**
   * {@inheritDoc}
   */
  public void visit(OrderSiblingsByClause expression) {
    super.visit(expression);
    // TODO
  }

  /**
   * {@inheritDoc}
   */
  public void visit(RegexpExpression expression) {
    super.visit(expression);
    // Nothing to validate semantically
  }

  /**
   * {@inheritDoc}
   */
  public void visit(StartWithClause expression) {
    super.visit(expression);
    // Nothing to validate semantically
  }

  /**
   * {@inheritDoc}
   */
  public void visit(TableExpression expression) {
    super.visit(expression);

    // No need to validate if no extension was defined
    if (extension != EclipseLinkSemanticValidatorExtension.NULL_EXTENSION) {
      Expression tableNameExpression = expression.getExpression();
      String tableName = literal(tableNameExpression, LiteralType.STRING_LITERAL);

      if (tableName != ExpressionTools.EMPTY_STRING) {
        tableName = ExpressionTools.unquote(tableName);

        if ((tableName.length() > 0) && !extension.tableExists(tableName)) {
          addProblem(tableNameExpression, JPQLQueryProblemMessages.TableExpression_InvalidTableName);
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public void visit(TableVariableDeclaration expression) {
    super.visit(expression);
    // Nothing to validate semantically
  }

  /**
   * {@inheritDoc}
   */
  public void visit(UnionClause expression) {
    super.visit(expression);
    // Nothing to validate semantically
  }

  /**
   * This visitor retrieves the clause owning the visited {@link Expression}.
   */
  public static class EclipseLinkOwningClauseVisitor extends OwningClauseVisitor {

    public UnionClause unionClause;

    /**
     * Creates a new <code>EclipseLinkOwningClauseVisitor</code>.
     */
    public EclipseLinkOwningClauseVisitor() {
      super();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void dispose() {
      super.dispose();
      unionClause = null;
    }

    public void visit(UnionClause expression) {
      this.unionClause = expression;
    }
  }

  protected static class SubquerySelectItemCalculator extends AnonymousExpressionVisitor {

    public int count;

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(BadExpression expression) {
      count = 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(CollectionExpression expression) {
      count = expression.childrenSize();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void visit(Expression expression) {
      count = 1;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(NullExpression expression) {
      count = 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SimpleSelectClause expression) {
      expression.getSelectExpression().accept(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SimpleSelectStatement expression) {
      expression.getSelectClause().accept(this);
    }
  }

  protected static class TableExpressionVisitor extends AbstractEclipseLinkExpressionVisitor {

    /**
     * The {@link Expression} being visited.
     */
    protected Expression expression;

    /**
     * <code>true</code> if the {@link Expression} being visited is a {@link TableExpression}.
     */
    protected boolean valid;

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(TableExpression expression) {
      valid = (this.expression == expression);
    }
  }

  protected class TopLevelFirstDeclarationVisitor extends AbstractSemanticValidator.TopLevelFirstDeclarationVisitor {

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(CollectionValuedPathExpression expression) {

      // Derived path is not allowed, this could although be a fully
      // qualified class name, which was added to EclipseLink 2.4
      EclipseLinkVersion version = EclipseLinkVersion.value(getProviderVersion());
      valid = version.isNewerThanOrEqual(EclipseLinkVersion.VERSION_2_4);

      if (valid) {
        Object type = helper.getType(expression.toActualText());
        valid = helper.isTypeResolvable(type);
      }
    }
  }
}
TOP

Related Classes of org.eclipse.persistence.jpa.jpql.AbstractEclipseLinkSemanticValidator$TableExpressionVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.