| /* |
| * Copyright (c) 2006, 2021 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 v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation |
| // |
| package org.eclipse.persistence.jpa.jpql.tools; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import org.eclipse.persistence.jpa.jpql.ExpressionTools; |
| import org.eclipse.persistence.jpa.jpql.JPAVersion; |
| import org.eclipse.persistence.jpa.jpql.LiteralType; |
| import org.eclipse.persistence.jpa.jpql.LiteralVisitor; |
| import org.eclipse.persistence.jpa.jpql.ParameterTypeVisitor; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractTraverseChildrenVisitor; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractTraverseParentVisitor; |
| import org.eclipse.persistence.jpa.jpql.parser.Expression; |
| import org.eclipse.persistence.jpa.jpql.parser.ExpressionRegistry; |
| 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.SimpleSelectStatement; |
| import org.eclipse.persistence.jpa.jpql.tools.resolver.Declaration; |
| import org.eclipse.persistence.jpa.jpql.tools.resolver.DeclarationResolver; |
| import org.eclipse.persistence.jpa.jpql.tools.resolver.Resolver; |
| import org.eclipse.persistence.jpa.jpql.tools.resolver.ResolverBuilder; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IManagedTypeProvider; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IMapping; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IQuery; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.IType; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.ITypeDeclaration; |
| import org.eclipse.persistence.jpa.jpql.tools.spi.ITypeRepository; |
| |
| /** |
| * This context is used to store information related to the JPQL query. |
| * |
| * <pre><code> {@link IQuery} externalQuery = ...; |
| * |
| * JPQLQueryContext context = new JPQLQueryContext(DefaultJPQLGrammar.instance()); |
| * context.setQuery(query);</code></pre> |
| * |
| * If the JPQL query is already parsed, then the context can use it and it needs to be set before |
| * setting the {@link IQuery}: |
| * <pre><code> {@link JPQLExpression} jpqlExpression = ...; |
| * |
| * JPQLQueryContext context = new JPQLQueryContext(DefaultJPQLGrammar.instance()); |
| * context.setJPQLExpression(jpqlExpression); |
| * context.setQuery(query);</code></pre> |
| * <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.3 |
| * @author Pascal Filion |
| */ |
| @SuppressWarnings("nls") |
| public abstract class JPQLQueryContext { |
| |
| /** |
| * This map caches the {@link JPQLQueryContext contexts} in order to keep them in memory and for |
| * fast access to the information of any query (top-level query and subqueries). |
| */ |
| private Map<Expression, JPQLQueryContext> contexts; |
| |
| /** |
| * The current {@link JPQLQueryContext} is the context used for the current query or subquery. |
| * If the current context is not the global context, then its parent is non <code>null</code>. |
| */ |
| protected JPQLQueryContext currentContext; |
| |
| /** |
| * The parsed {@link Expression JPQL Expression} currently visited. |
| */ |
| private Expression currentQuery; |
| |
| /** |
| * The resolver of the current query's declaration. For a <b>SELECT</b> query, it contains the |
| * information defined in the <b>FROM</b> clause. For <b>DELETE</b> and <b>UPDATE</b> queries, |
| * it contains a single range declaration variable. |
| */ |
| private DeclarationResolver declarationResolver; |
| |
| /** |
| * This visitor is responsible to find the {@link InputParameter} with a specific named parameter |
| * or positional parameter. |
| */ |
| private InputParameterVisitor inputParameterVisitor; |
| |
| /** |
| * The parsed tree representation of the JPQL query. |
| */ |
| private JPQLExpression jpqlExpression; |
| |
| /** |
| * The grammar that defines how to parse a JPQL query. |
| */ |
| private JPQLGrammar jpqlGrammar; |
| |
| /** |
| * This visitor is used to retrieve a variable name from various type of {@link |
| * org.eclipse.persistence.jpa.jpql.parser.Expression JPQL Expression}. |
| */ |
| private LiteralVisitor literalVisitor; |
| |
| /** |
| * This visitor is responsible to calculate the closest type of any input parameter. |
| */ |
| private ParameterTypeVisitor parameterTypeVisitor; |
| |
| /** |
| * When this context is a sub-context used for a subquery, then this is the context for the |
| * parent query. |
| */ |
| protected JPQLQueryContext parent; |
| |
| /** |
| * The external form of the JPQL query being manipulated. |
| */ |
| private IQuery query; |
| |
| /** |
| * This visitor is responsible to retrieve the {@link Expression} that is the beginning of a |
| * query. For a subquery, it will retrieve {@link SimpleSelectStatement} and for a top-level |
| * query, it will retrieve {@link JPQLExpression}. The search goes through the parent hierarchy. |
| */ |
| private QueryExpressionVisitor queryExpressionVisitor; |
| |
| /** |
| * This visitor creates a {@link Resolver} that gives information about the visited {@link |
| * Expression}. The actual {@link Resolver} will calculate the proper {@link IType} as well. |
| */ |
| private ResolverBuilder resolverBuilder; |
| |
| /** |
| * Determines if the parsing system should be tolerant, meaning if it should try to parse invalid |
| * or incomplete queries. |
| */ |
| private boolean tolerant; |
| |
| /** |
| * Internal flag used to determine if the declaration portion of the query was visited. |
| */ |
| private boolean traversed; |
| |
| /** |
| * Creates a new <code>JPQLQueryContext</code>. |
| * |
| * @param jpqlGrammar The {@link JPQLGrammar} defines how to parse a JPQL query |
| */ |
| public JPQLQueryContext(JPQLGrammar jpqlGrammar) { |
| super(); |
| initialize(jpqlGrammar); |
| } |
| |
| /** |
| * Creates a new sub-<code>JPQLQueryContext</code>. |
| * |
| * @param parent The parent context |
| * @param currentQuery The parsed tree representation of the subquery |
| */ |
| protected JPQLQueryContext(JPQLQueryContext parent, Expression currentQuery) { |
| this(parent.jpqlGrammar); |
| store(parent, currentQuery); |
| } |
| |
| protected DeclarationResolver buildDeclarationResolver() { |
| DeclarationResolver parentResolver = (parent != null) ? parent.getDeclarationResolverImp() : null; |
| return buildDeclarationResolver(parentResolver); |
| } |
| |
| protected DeclarationResolver buildDeclarationResolver(DeclarationResolver parent) { |
| return new DeclarationResolver(parent, this); |
| } |
| |
| protected InputParameterVisitor buildInputParameter() { |
| return new InputParameterVisitor(); |
| } |
| |
| protected abstract JPQLQueryContext buildJPQLQueryContext(JPQLQueryContext currentContext, |
| Expression currentQuery); |
| |
| protected abstract LiteralVisitor buildLiteralVisitor(); |
| |
| protected abstract ParameterTypeVisitor buildParameterTypeVisitor(); |
| |
| protected QueryExpressionVisitor buildQueryExpressionVisitor() { |
| return new QueryExpressionVisitor(); |
| } |
| |
| protected abstract ResolverBuilder buildResolverBuilder(); |
| |
| /** |
| * Converts the given {@link Declaration} from being set as a range variable declaration to |
| * a path expression declaration. |
| * <p> |
| * In this query "<code>UPDATE Employee SET firstName = 'MODIFIED' WHERE (SELECT COUNT(m) FROM |
| * managedEmployees m) {@literal >} 0</code>" <em>managedEmployees</em> is an unqualified |
| * collection-valued path expression (<code>employee.managedEmployees</code>). |
| * |
| * @param declaration The {@link Declaration} that was parsed to range over an abstract schema |
| * name but is actually ranging over a path expression |
| */ |
| public void convertUnqualifiedDeclaration(Declaration declaration) { |
| |
| if (parent != null) { |
| |
| // Retrieve the range identification variable from the parent declaration |
| Declaration parentDeclaration = parent.getDeclarationResolverImp().getDeclarations().get(0); |
| String outerVariableName = parentDeclaration.getVariableName(); |
| |
| // Qualify the range expression to be fully qualified |
| getDeclarationResolverImp().convertUnqualifiedDeclaration(declaration, outerVariableName); |
| } |
| } |
| |
| /** |
| * Disposes the internal data. |
| */ |
| public void dispose() { |
| |
| query = null; |
| traversed = false; |
| currentQuery = null; |
| currentContext = this; |
| jpqlExpression = null; |
| |
| contexts.clear(); |
| |
| if (declarationResolver != null) { |
| declarationResolver.dispose(); |
| } |
| } |
| |
| /** |
| * Disposes this context, which is the current context being used by a subquery. Once it is |
| * disposed, any information retrieved will be for the subquery's parent query. |
| */ |
| public void disposeSubqueryContext() { |
| currentContext = currentContext.parent; |
| } |
| |
| /** |
| * Retrieves all the {@link InputParameter InputParameters} with the given parameter name. |
| * |
| * @param parameterName The parameter used to find the {@link InputParameter InputParameters} |
| * with the same value |
| * @return Either the {@link InputParameter InputParameters} that has the given parameter or an |
| * empty collection |
| */ |
| public Collection<InputParameter> findInputParameters(String parameterName) { |
| |
| InputParameterVisitor visitor = getInputParameterVisitor(); |
| |
| try { |
| visitor.parameterName = parameterName; |
| visitor.inputParameters = new ArrayList<>(); |
| |
| jpqlExpression.accept(visitor); |
| |
| return visitor.inputParameters; |
| } |
| finally { |
| visitor.parameterName = null; |
| visitor.inputParameters = null; |
| } |
| } |
| |
| /** |
| * Returns the current {@link Expression} being manipulated, which is either the top-level query |
| * or a subquery. |
| * |
| * @return Either the top-level query or a subquery |
| */ |
| public Expression getActualCurrentQuery() { |
| return currentQuery; |
| } |
| |
| /** |
| * Returns the {@link DeclarationResolver} of this context and not from the current query's |
| * declaration. |
| * |
| * @return The {@link DeclarationResolver} for this context |
| */ |
| public DeclarationResolver getActualDeclarationResolver() { |
| return getDeclarationResolverImp(); |
| } |
| |
| /** |
| * Returns the current {@link JPQLQueryContext}, i.e. the context of the query being manipulated, |
| * which can either be the top-level query or a subquery. |
| * |
| * @return The active context |
| */ |
| public JPQLQueryContext getCurrentContext() { |
| return currentContext; |
| } |
| |
| /** |
| * Returns the current {@link Expression} being manipulated, which is either the top-level query |
| * or a subquery. |
| * |
| * @return Either the top-level query or a subquery |
| */ |
| public Expression getCurrentQuery() { |
| return currentContext.currentQuery; |
| } |
| |
| /** |
| * Retrieves the {@link Declaration} for which the given variable name is used to navigate to the |
| * "root" object. This does not go up the hierarchy when looking for the {@link Declaration}. |
| * |
| * @param variableName The name of the identification variable that is used to navigate a "root" object |
| * @return The {@link Declaration} containing the information about the identification variable declaration |
| * @since 2.5 |
| */ |
| public Declaration getDeclaration(String variableName) { |
| return getDeclarationResolver().getDeclaration(variableName); |
| } |
| |
| /** |
| * Returns the {@link DeclarationResolver} of the current query's declaration. For a |
| * <b>SELECT</b> query, it contains the information defined in the <b>FROM</b> clause. For |
| * <b>DELETE</b> and <b>UPDATE</b> queries, it contains a single range declaration variable. If |
| * the current query is a subquery, then it contains the information defined in the |
| * <code>FROM</code> clause. |
| * |
| * @return The {@link DeclarationResolver} for the current query being visited |
| */ |
| public DeclarationResolver getDeclarationResolver() { |
| return currentContext.getDeclarationResolverImp(); |
| } |
| |
| /** |
| * Returns the {@link DeclarationResolver} of the current query's declaration. For a |
| * <b>SELECT</b> query, it contains the information defined in the <b>FROM</b> clause. For |
| * <b>DELETE</b> and <b>UPDATE</b> queries, it contains a single range variable declaration. If |
| * the current query is a subquery, then it contains the information defined in the subquery |
| * <code>FROM</code> clause. |
| * |
| * @param expression The {@link Expression} that will be used to retrieve its query expression, |
| * i.e. either {@link JPQLExpression} or {@link SimpleSelectStatement} |
| * @return The {@link DeclarationResolver} for the current query being visited |
| */ |
| public DeclarationResolver getDeclarationResolver(Expression expression) { |
| |
| // Retrieve the expression for the query (either the top-level query or subquery) |
| // owning the given Expression |
| expression = getQueryExpression(expression); |
| |
| // Retrieve the cached JPQLQueryContext |
| JPQLQueryContext context = contexts.get(expression); |
| |
| if (context != null) { |
| return context.getDeclarationResolverImp(); |
| } |
| |
| // The JPQLQueryContext has not been created yet, |
| // create the parent JPQLQueryContexts first |
| getDeclarationResolver(expression.getParent()); |
| |
| // Create the JPQLQueryContext and DeclarationResolver for the subquery |
| newSubqueryContext(expression); |
| return currentContext.getDeclarationResolverImp(); |
| } |
| |
| /** |
| * Returns the {@link DeclarationResolver} of the current query's declaration. |
| * |
| * @return The {@link DeclarationResolver} for the current query being visited |
| */ |
| protected DeclarationResolver getDeclarationResolverImp() { |
| |
| if (declarationResolver == null) { |
| declarationResolver = buildDeclarationResolver(); |
| } |
| |
| // Only traverse the declaration once |
| if (!traversed) { |
| traversed = true; |
| declarationResolver.populate(currentQuery); |
| } |
| |
| return declarationResolver; |
| } |
| |
| /** |
| * Returns the ordered list of {@link Declaration Declarations}. |
| * |
| * @return The {@link Declaration Declarations} of the current query that was parsed |
| */ |
| public List<Declaration> getDeclarations() { |
| return getDeclarationResolver().getDeclarations(); |
| } |
| |
| /** |
| * Returns the {@link IType} representing the possible given enum type. If the type name |
| * |
| * @param enumTypeName The fully qualified enum type with the constant |
| * @return The external form for the given Enum type |
| */ |
| public IType getEnumType(String enumTypeName) { |
| return getTypeRepository().getEnumType(enumTypeName); |
| } |
| |
| /** |
| * Returns the registry containing the {@link org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF |
| * JPQLQueryBNFs} and the {@link org.eclipse.persistence.jpa.jpql.parser.ExpressionFactory |
| * ExpressionFactories} that are used to properly parse a JPQL query. |
| * |
| * @return The registry containing the information related to the JPQL grammar |
| */ |
| public ExpressionRegistry getExpressionRegistry() { |
| return jpqlGrammar.getExpressionRegistry(); |
| } |
| |
| /** |
| * Returns the JPQL grammar that will be used to define how to parse a JPQL query. |
| * |
| * @return The grammar that was used to parse this {@link Expression} |
| */ |
| public JPQLGrammar getGrammar() { |
| return jpqlGrammar; |
| } |
| |
| protected InputParameterVisitor getInputParameterVisitor() { |
| if (inputParameterVisitor == null) { |
| inputParameterVisitor = buildInputParameter(); |
| } |
| return inputParameterVisitor; |
| } |
| |
| /** |
| * Returns the version of the Java Persistence to support, which dictates which version of the |
| * JPQL grammar to support. |
| * |
| * @return The version of the supported Java Persistence functional specification |
| */ |
| public JPAVersion getJPAVersion() { |
| return jpqlGrammar.getJPAVersion(); |
| } |
| |
| /** |
| * Returns the parsed tree representation of the JPQL query. |
| * |
| * @return The parsed tree representation of the JPQL query |
| */ |
| public JPQLExpression getJPQLExpression() { |
| return jpqlExpression; |
| } |
| |
| /** |
| * Returns the string representation of the JPQL query. |
| * |
| * @return The string representation of the JPQL query |
| */ |
| public String getJPQLQuery() { |
| return query.getExpression(); |
| } |
| |
| protected LiteralVisitor getLiteralVisitor() { |
| if (literalVisitor == null) { |
| literalVisitor = buildLiteralVisitor(); |
| } |
| return literalVisitor; |
| } |
| |
| /** |
| * Returns the {@link IMapping} for the field represented by the given {@link Expression}. |
| * |
| * @param expression The {@link Expression} representing a state field path expression or a |
| * collection-valued path expression |
| * @return Either the {@link IMapping} or <code>null</code> if none exists |
| */ |
| public IMapping getMapping(Expression expression) { |
| return getResolver(expression).getMapping(); |
| } |
| |
| /** |
| * Retrieves, if it can be determined, the type of the given {@link InputParameter}. The type |
| * will be guessed based on its location within expression. |
| * <p> |
| * Note: Both named and positional input parameter can be used. |
| * |
| * @param inputParameter The {@link InputParameter} to retrieve its type |
| * @return Either the closest type of the input parameter or <code>null</code> if the type could |
| * not be determined |
| */ |
| public IType getParameterType(InputParameter inputParameter) { |
| ParameterTypeVisitor visitor = getParameterTypeVisitor(); |
| try { |
| inputParameter.accept(visitor); |
| return (IType) visitor.getType(); |
| } |
| finally { |
| visitor.dispose(); |
| } |
| } |
| |
| protected ParameterTypeVisitor getParameterTypeVisitor() { |
| if (parameterTypeVisitor == null) { |
| parameterTypeVisitor = buildParameterTypeVisitor(); |
| } |
| return parameterTypeVisitor; |
| } |
| |
| /** |
| * Returns the parent context if the current context is not the root context. |
| * |
| * @return The parent context or <code>null</code> if the current context is the root |
| */ |
| public JPQLQueryContext getParent() { |
| return parent; |
| } |
| |
| /** |
| * Retrieves the provider of managed types. |
| * |
| * @return The object that has access to the application's managed types. |
| */ |
| public IManagedTypeProvider getProvider() { |
| return query.getProvider(); |
| } |
| |
| /** |
| * Returns the version of the persistence provider. |
| * |
| * @return The version of the persistence provider, if one is extending the default JPQL grammar |
| * defined in the Java Persistence specification, otherwise returns an empty string |
| * @since 2.5 |
| */ |
| public String getProviderVersion() { |
| return jpqlGrammar.getProviderVersion(); |
| } |
| |
| /** |
| * Returns the external form of the JPQL query. |
| * |
| * @return The external form of the JPQL query |
| */ |
| public IQuery getQuery() { |
| return query; |
| } |
| |
| /** |
| * Retrieves the {@link Expression} representing the query statement (either the top-level query |
| * {@link JPQLExpression} or the subquery {@link SimpleSelectStatement}) owning the given {@link |
| * Expression}. |
| * |
| * @param expression A child of the "root" {@link Expression} to retrieve |
| * @return The query statement that is the "root" parent of the given {@link Expression} |
| */ |
| public Expression getQueryExpression(Expression expression) { |
| QueryExpressionVisitor visitor = getQueryExpressionVisitor(); |
| try { |
| expression.accept(visitor); |
| return visitor.expression; |
| } |
| finally { |
| visitor.expression = null; |
| } |
| } |
| |
| protected QueryExpressionVisitor getQueryExpressionVisitor() { |
| if (queryExpressionVisitor == null) { |
| queryExpressionVisitor = buildQueryExpressionVisitor(); |
| } |
| return queryExpressionVisitor; |
| } |
| |
| /** |
| * Creates or retrieved the cached {@link Resolver} for the given {@link Expression}. The |
| * {@link Resolver} can return the {@link IType} and {@link ITypeDeclaration} of the {@link |
| * Expression} and either the {@link org.eclipse.persistence.jpa.jpql.tools.spi.IManagedType IManagedType} |
| * or the {@link IMapping}. |
| * |
| * @param expression The {@link Expression} for which its {@link Resolver} will be retrieved |
| * @return {@link Resolver} for the given {@link Expression} |
| */ |
| public Resolver getResolver(Expression expression) { |
| ResolverBuilder visitor = getResolverBuilder(); |
| try { |
| expression.accept(visitor); |
| return visitor.getResolver(); |
| } |
| finally { |
| visitor.dispose(); |
| } |
| } |
| |
| /** |
| * Retrieves the {@link Resolver} mapped with the given identification variable. If the |
| * identification is not defined in the declaration traversed by this resolver, than the search |
| * will traverse the parent hierarchy. |
| * |
| * @param variableName The identification variable that maps a {@link Resolver} |
| * @return The {@link Resolver} mapped with the given identification variable |
| */ |
| public Resolver getResolver(String variableName) { |
| return getDeclarationResolver().getResolver(variableName); |
| } |
| |
| protected ResolverBuilder getResolverBuilder() { |
| if (resolverBuilder == null) { |
| resolverBuilder = buildResolverBuilder(); |
| } |
| return resolverBuilder; |
| } |
| |
| /** |
| * Returns the variables that got defined in the select expression. This only applies to JPQL |
| * queries built for JPA 2.0. |
| * |
| * @return The variables identifying the select expressions, if any was defined or an empty set |
| * if none were defined |
| */ |
| public Set<String> getResultVariables() { |
| return getDeclarationResolver().getResultVariables(); |
| } |
| |
| /** |
| * Retrieves the external type for the given Java type. |
| * |
| * @param type The Java type to wrap with an external form |
| * @return The external form of the given type |
| */ |
| public IType getType(Class<?> type) { |
| return getTypeRepository().getType(type); |
| } |
| |
| /** |
| * Returns the {@link IType} of the given {@link Expression}. |
| * |
| * @param expression The {@link Expression} for which its type will be calculated |
| * @return Either the {@link IType} that was resolved by this {@link Resolver} or the |
| * {@link IType} for {@link IType#UNRESOLVABLE_TYPE} if it could not be resolved |
| */ |
| public IType getType(Expression expression) { |
| return getResolver(expression).getType(); |
| } |
| |
| /** |
| * Retrieves the external class with the given fully qualified class name. |
| * |
| * @param typeName The fully qualified class name of the class to retrieve |
| * @return The external form of the class to retrieve |
| */ |
| public IType getType(String typeName) { |
| return getTypeRepository().getType(typeName); |
| } |
| |
| /** |
| * Returns the {@link ITypeDeclaration} of the field handled by this {@link Resolver}. |
| * |
| * @param expression The {@link Expression} for which its type declaration will be calculated |
| * @return Either the {@link ITypeDeclaration} that was resolved by this {@link Resolver} or the |
| * {@link ITypeDeclaration} for {@link IType#UNRESOLVABLE_TYPE} if it could not be resolved |
| */ |
| public ITypeDeclaration getTypeDeclaration(Expression expression) { |
| return getResolver(expression).getTypeDeclaration(); |
| } |
| |
| /** |
| * Returns a helper that gives access to the most common {@link IType types}. |
| * |
| * @return A helper containing a collection of methods related to {@link IType} |
| */ |
| public TypeHelper getTypeHelper() { |
| return getTypeRepository().getTypeHelper(); |
| } |
| |
| /** |
| * Returns the type repository for the application. |
| * |
| * @return The repository of {@link IType ITypes} |
| */ |
| public ITypeRepository getTypeRepository() { |
| return getProvider().getTypeRepository(); |
| } |
| |
| /** |
| * Determines whether the JPQL expression has <b>JOIN</b> expressions. |
| * |
| * @return <code>true</code> if the query or subquery being traversed contains <b>JOIN</b> |
| * expressions; <code>false</code> otherwise |
| */ |
| public boolean hasJoins() { |
| return getDeclarationResolver().hasJoins(); |
| } |
| |
| /** |
| * Initializes this {@link JPQLQueryContext}. |
| * |
| * @param jpqlGrammar The grammar that defines how to parse a JPQL query |
| */ |
| protected void initialize(JPQLGrammar jpqlGrammar) { |
| this.tolerant = true; |
| this.currentContext = this; |
| this.jpqlGrammar = jpqlGrammar; |
| this.contexts = new HashMap<>(); |
| } |
| |
| /** |
| * Initializes the parsed tree representation of the JPQL query if it has not been set before |
| * setting the {@link IQuery}. |
| */ |
| protected void initializeRoot() { |
| |
| if (jpqlExpression == null) { |
| jpqlExpression = new JPQLExpression(query.getExpression(), jpqlGrammar, tolerant); |
| } |
| |
| currentQuery = jpqlExpression; |
| contexts.put(currentQuery, this); |
| } |
| |
| /** |
| * Determines whether the given identification variable is defining a join or a collection member |
| * declaration expressions. |
| * |
| * @param variableName The identification variable to check for what it maps |
| * @return <code>true</code> if the given identification variable maps a collection-valued field |
| * defined in a <code>JOIN</code> or <code>IN</code> expression; <code>false</code> if it's not |
| * defined or it's mapping an abstract schema name |
| */ |
| public boolean isCollectionIdentificationVariable(String variableName) { |
| return getDeclarationResolver().isCollectionIdentificationVariable(variableName); |
| } |
| |
| /** |
| * Determines whether the given variable name is an identification variable name used to define |
| * an abstract schema name. |
| * |
| * @param variableName The name of the variable to verify if it's defined in a range variable |
| * declaration in the current query or any parent query |
| * @return <code>true</code> if the variable name is mapping an abstract schema name; <code>false</code> |
| * if it's defined in a collection member declaration |
| */ |
| public boolean isRangeIdentificationVariable(String variableName) { |
| return getDeclarationResolver().isRangeIdentificationVariable(variableName); |
| } |
| |
| /** |
| * Determines if the given variable is a result variable. |
| * |
| * @param variable The variable to check if it's a result variable |
| * @return <code>true</code> if the given variable is defined as a result variable; <code>false</code> |
| * otherwise |
| */ |
| public boolean isResultVariable(String variable) { |
| return getDeclarationResolverImp().isResultVariable(variable); |
| } |
| |
| /** |
| * Determines whether this {@link JPQLQueryContext} currently holds the information of a subquery |
| * or for the top-level query. |
| * |
| * @return <code>true</code> if the current context is for a subquery; <code>false</code> for the |
| * top-level query |
| * @since 2.5 |
| */ |
| public boolean isSubquery() { |
| return currentContext.parent != null; |
| } |
| |
| /** |
| * Determines if the parser is in tolerant mode or is in fast mode. When the tolerant is turned |
| * on, it means the parser will attempt to parse incomplete or invalid queries. |
| * |
| * @return <code>true</code> if the parsing system should parse invalid or incomplete queries; |
| * <code>false</code> when the query is well-formed and valid |
| * @since 2.5 |
| */ |
| public boolean isTolerant() { |
| return tolerant; |
| } |
| |
| /** |
| * Retrieves the "literal" from the given {@link Expression}. The literal to retrieve depends on |
| * the given {@link LiteralType type}. The literal is basically a string value like an |
| * identification variable name, an input parameter, a path expression, an abstract schema name, |
| * etc. |
| * |
| * @param expression The {@link Expression} to visit |
| * @param type The {@link LiteralType} helps to determine what to retrieve from the visited |
| * {@link Expression} |
| * @return A value from the given {@link Expression} or an empty string if the given {@link |
| * Expression} and the {@link LiteralType} do not match |
| */ |
| public String literal(Expression expression, LiteralType type) { |
| LiteralVisitor visitor = getLiteralVisitor(); |
| try { |
| visitor.setType(type); |
| expression.accept(visitor); |
| return visitor.literal; |
| } |
| finally { |
| visitor.literal = ExpressionTools.EMPTY_STRING; |
| } |
| } |
| |
| /** |
| * Changes the state of this context to use the given subquery. |
| * |
| * @param currentQuery The parsed tree representation of the subquery that will become the |
| * current query |
| * @see #disposeSubqueryContext() |
| */ |
| public void newSubqueryContext(Expression currentQuery) { |
| |
| JPQLQueryContext context = contexts.get(currentQuery); |
| |
| if (context != null) { |
| currentContext = context; |
| } |
| else { |
| currentContext = buildJPQLQueryContext(currentContext, currentQuery); |
| } |
| } |
| |
| /** |
| * Sets the parsed tree representation of the JPQL query. If the expression was parsed outside of |
| * the scope of this context, then this method has to be invoked before {@link #setQuery(IQuery)} |
| * because the JPQL query is automatically parsed by that method. |
| * |
| * @param jpqlExpression The parsed representation of the JPQL query to manipulate |
| * @see #setQuery(IQuery) |
| */ |
| public void setJPQLExpression(JPQLExpression jpqlExpression) { |
| this.jpqlExpression = jpqlExpression; |
| } |
| |
| /** |
| * Sets the external form of the JPQL query, which will be parsed and information will be |
| * extracted for later access. |
| * |
| * @param query The external form of the JPQL query |
| * @see #setJPQLExpression(JPQLExpression) |
| */ |
| public void setQuery(IQuery query) { |
| this.query = query; |
| initializeRoot(); |
| } |
| |
| /** |
| * Sets whether the parser is in tolerant mode or is in fast mode. When the tolerant is turned |
| * on, it means the parser will attempt to parse incomplete or invalid queries. |
| * <p> |
| * Note: This needs to be set before {@link #setQuery(IQuery)} or {@link #setJPQLExpression(JPQLExpression)} |
| * is invoked. |
| * |
| * @param tolerant Determines if the parsing system should be tolerant, meaning if it should try |
| * to parse invalid or incomplete queries |
| * @since 2.5 |
| */ |
| public void setTolerant(boolean tolerant) { |
| this.tolerant = tolerant; |
| } |
| |
| /** |
| * Stores the information contained in the given parent into this one. |
| * |
| * @param parent The parent context, which is the context of the parent query |
| * @param currentQuery The subquery becoming the current query |
| */ |
| protected void store(JPQLQueryContext parent, Expression currentQuery) { |
| |
| this.parent = parent; |
| |
| // Copy the information from the parent to this one, only a single instance is required |
| this.currentQuery = currentQuery; |
| this.query = parent.query; |
| this.contexts = parent.contexts; |
| this.jpqlExpression = parent.jpqlExpression; |
| this.literalVisitor = parent.literalVisitor; |
| this.resolverBuilder = parent.resolverBuilder; |
| this.parameterTypeVisitor = parent.parameterTypeVisitor; |
| this.queryExpressionVisitor = parent.queryExpressionVisitor; |
| |
| // Cache the context |
| this.contexts.put(currentQuery, this); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| if (parent != null) { |
| sb.append("Subquery="); |
| } |
| else { |
| sb.append("Top-Level Query="); |
| } |
| sb.append(currentQuery.toParsedText()); |
| return sb.toString(); |
| } |
| |
| /** |
| * This visitor is responsible to find the {@link InputParameter InputParameters} with a certain |
| * parameter name. |
| */ |
| protected class InputParameterVisitor extends AbstractTraverseChildrenVisitor { |
| |
| /** |
| * The collection of {@link InputParameter InputParameters} that was retrieved by traversing the |
| * parsed tree. |
| */ |
| protected Collection<InputParameter> inputParameters; |
| |
| /** |
| * The name of the input parameter to search. |
| */ |
| protected String parameterName; |
| |
| @Override |
| public void visit(InputParameter expression) { |
| if (parameterName.equals(expression.getParameter())) { |
| inputParameters.add(expression); |
| } |
| } |
| |
| @Override |
| public void visit(SimpleSelectStatement expression) { |
| newSubqueryContext(expression); |
| try { |
| super.visit(expression); |
| } |
| finally { |
| disposeSubqueryContext(); |
| } |
| } |
| } |
| |
| /** |
| * This visitor is responsible to retrieve the {@link Expression} that is the beginning of a |
| * query. For a subquery, it will retrieve {@link SimpleSelectStatement} and for a top-level |
| * query, it will retrieve {@link JPQLExpression}. The search goes through the parent hierarchy. |
| */ |
| protected static class QueryExpressionVisitor extends AbstractTraverseParentVisitor { |
| |
| /** |
| * The {@link Expression} that is the beginning of a query. |
| */ |
| protected Expression expression; |
| |
| @Override |
| public void visit(JPQLExpression expression) { |
| this.expression = expression; |
| } |
| |
| @Override |
| public void visit(SimpleSelectStatement expression) { |
| this.expression = expression; |
| } |
| } |
| } |