blob: 9bac2c45e244b0642cac7696418149a192b654fd [file] [log] [blame]
/*
* 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.
* <p>
* For instance, from the following JPQL query:
* <p>
* <pre><code> SELECT new test.example.Employee(e.name, e.id),
* new test.example.Address(a.zipcode)
* FROM Employee e, Address a</code></pre>
* <p>
* 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:
* <p>
* <pre><code> SELECT new test.example.Address(a.streetName, a.zipcode)
* FROM Address a</code></pre>
* <p>
* 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);
}
}
}