| /* |
| * Copyright (c) 2012, 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.internal.jpa.jpql; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.jpa.jpql.Assert; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractEclipseLinkTraverseChildrenVisitor; |
| import org.eclipse.persistence.jpa.jpql.parser.DefaultEclipseLinkJPQLGrammar; |
| import org.eclipse.persistence.jpa.jpql.parser.DeleteClause; |
| import org.eclipse.persistence.jpa.jpql.parser.Expression; |
| import org.eclipse.persistence.jpa.jpql.parser.FromClause; |
| 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.parser.UpdateClause; |
| import org.eclipse.persistence.queries.DatabaseQuery; |
| |
| /** |
| * This helper can perform various operations over a JPQL query. |
| * |
| * @version 2.5 |
| * @since 2.5 |
| * @author Pascal Filion |
| */ |
| public class JPQLQueryHelper { |
| |
| /** |
| * The grammar that defines how to parse a JPQL query. |
| */ |
| private JPQLGrammar jpqlGrammar; |
| |
| /** |
| * Creates a new <code>JPQLQueryHelper</code> which uses the latest JPQL grammar. |
| */ |
| public JPQLQueryHelper() { |
| this(DefaultEclipseLinkJPQLGrammar.instance()); |
| } |
| |
| /** |
| * Creates a new <code>JPQLQueryHelper</code>. |
| * |
| * @param jpqlGrammar The {@link JPQLGrammar} that will determine how to parse JPQL queries |
| * @exception NullPointerException The {@link JPQLGrammar} cannot be <code>null</code> |
| */ |
| public JPQLQueryHelper(JPQLGrammar jpqlGrammar) { |
| super(); |
| Assert.isNotNull(jpqlGrammar, "The JPQLGrammar cannot be null"); |
| this.jpqlGrammar = jpqlGrammar; |
| } |
| |
| /** |
| * Retrieves the list of {@link ClassDescriptor descriptors} corresponding to the entities used |
| * throughout the given JPQL query. |
| * |
| * @param jpqlQuery The JPQL query used to gather the descriptors defined in the declaration clause |
| * @param session The {@link AbstractSession} is used to retrieve the descriptors |
| * @return The list of {@link ClassDescriptor descriptors} used in the JPQL query or an empty |
| * list if the JPQL query is invalid or incomplete |
| */ |
| public List<ClassDescriptor> getClassDescriptors(CharSequence jpqlQuery, AbstractSession session) { |
| |
| // Parse the JPQL query |
| JPQLExpression jpqlExpression = new JPQLExpression(jpqlQuery, jpqlGrammar, true); |
| |
| // Create the context and cache the information |
| JPQLQueryContext queryContext = new JPQLQueryContext(jpqlGrammar); |
| queryContext.cache(session, null, jpqlExpression, jpqlQuery); |
| |
| // Visit the JPQL query and collect the descriptors defined in the declaration clauses |
| DescriptorCollector collector = new DescriptorCollector(queryContext); |
| jpqlExpression.accept(collector); |
| |
| return new ArrayList<>(collector.descriptors); |
| } |
| |
| /** |
| * Retrieves the class names and the attribute names mapped to their types that are used in the |
| * constructor expressions defined in the <code><b>SELECT</b></code> clause. |
| * <br> |
| * For instance, from the following JPQL query: |
| * <br> |
| * <pre><code> SELECT new test.example.Employee(e.name, e.id), |
| * new test.example.Address(a.zipcode) |
| * FROM Employee e, Address a</code></pre> |
| * <br> |
| * The return object is |
| * <pre><code> |- test.example.Employee |
| * |- |- name : String |
| * |- |- id : int |
| * |- test.example.Address |
| * |- zipcode : int</code></pre> |
| * |
| * @param session The {@link AbstractSession} is used to retrieve the mappings used in the |
| * constructor items |
| * @return The holder of the result |
| */ |
| public List<ConstructorQueryMappings> getConstructorQueryMappings(AbstractSession session) { |
| |
| List<ConstructorQueryMappings> allMappings = new LinkedList<>(); |
| |
| for (DatabaseQuery query : session.getJPAQueries()) { |
| ConstructorQueryMappings mappings = getConstructorQueryMappings(session, query); |
| allMappings.add(mappings); |
| } |
| |
| return allMappings; |
| } |
| |
| /** |
| * Retrieves the class names and the attribute names mapped to their types that are used in the |
| * constructor expressions defined in the <code><b>SELECT</b></code> clause. |
| * <p> |
| * For instance, from the following JPQL query: |
| * <br> |
| * <pre><code> SELECT new test.example.Address(a.streetName, a.zipcode) |
| * FROM Address a</code></pre> |
| * <br> |
| * The return object is |
| * <pre><code> test.example.Address |
| * |- BasicMapping(streetName) : String |
| * |- BasicMapping(zipcode) : int</code></pre> |
| * |
| * @param session The {@link AbstractSession} is used to retrieve the mappings used in the |
| * constructor items |
| * @return The holder of the result |
| */ |
| public ConstructorQueryMappings getConstructorQueryMappings(AbstractSession session, |
| DatabaseQuery query) { |
| |
| JPQLQueryContext queryContext = new JPQLQueryContext(query, jpqlGrammar); |
| |
| ConstructorQueryMappings mappings = new ConstructorQueryMappings(query); |
| mappings.populate(jpqlGrammar); |
| return mappings; |
| } |
| |
| /** |
| * Returns the JPQL grammar that will be used to define how to parse a JPQL query. |
| * |
| * @return The grammar that will determine how to parse a JPQL query |
| */ |
| public JPQLGrammar getGrammar() { |
| return jpqlGrammar; |
| } |
| |
| private static class DescriptorCollector extends AbstractEclipseLinkTraverseChildrenVisitor { |
| |
| private Set<ClassDescriptor> descriptors; |
| private JPQLQueryContext queryContext; |
| private DeclarationResolver resolver; |
| |
| /** |
| * Creates a new <code>DescriptorCollector</code>. |
| */ |
| private DescriptorCollector(JPQLQueryContext queryContext) { |
| super(); |
| this.descriptors = new HashSet<>(); |
| this.queryContext = queryContext; |
| } |
| |
| private void collectDescriptors(Expression expression, DeclarationResolver resolver) { |
| |
| resolver.populate(expression); |
| |
| for (Declaration declaration : resolver.getDeclarations()) { |
| ClassDescriptor descriptor = declaration.getDescriptor(); |
| if (descriptor != null) { |
| descriptors.add(descriptor); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(DeleteClause expression) { |
| resolver = new DeclarationResolver(queryContext, null); |
| collectDescriptors(expression, resolver); |
| } |
| |
| @Override |
| public void visit(FromClause expression) { |
| resolver = new DeclarationResolver(queryContext, null); |
| collectDescriptors(expression, resolver); |
| } |
| |
| @Override |
| public void visit(SimpleSelectStatement expression) { |
| resolver = new DeclarationResolver(queryContext, resolver); |
| collectDescriptors(expression, resolver); |
| resolver = resolver.getParent(); |
| } |
| |
| @Override |
| public void visit(UpdateClause expression) { |
| resolver = new DeclarationResolver(queryContext, null); |
| collectDescriptors(expression, resolver); |
| } |
| } |
| } |