HHH-16887 also validate arguments of @NamedQuery if @CheckHQL is specified
This commit is contained in:
parent
13877a9a3e
commit
28b1670d18
|
@ -19,29 +19,32 @@ import static java.lang.annotation.RetentionPolicy.CLASS;
|
|||
/**
|
||||
* Indicates that a package or top-level type contains HQL or JPQL
|
||||
* queries encoded as static strings that should be validated at
|
||||
* compile time by the Hibernate Query Validator. The Query Validator
|
||||
* must be enabled as an annotation processor in the project build.
|
||||
* Otherwise, this annotation has no effect.
|
||||
* compile time by the Metamodel Generator or Query Validator.
|
||||
* Errors in queries are reported by the Java compiler.
|
||||
* <p>
|
||||
* Within a scope annotated {@code @CheckHQL}, any static string
|
||||
* argument to any one the methods:
|
||||
* The Metamodel Generator or Query Validator must be enabled as an
|
||||
* annotation processor in the project build. Otherwise, if neither
|
||||
* is enabled, this annotation has no effect.
|
||||
* <p>
|
||||
* If only the Metamodel Generator is enabled, only arguments to the
|
||||
* following annotations are validated:
|
||||
* <ul>
|
||||
* <li>{@link jakarta.persistence.NamedQuery#query},
|
||||
* <li>{@link org.hibernate.annotations.NamedQuery#query}.
|
||||
* </ul>
|
||||
* <p>
|
||||
* Otherwise, if the Query validator is enabled, then, within the
|
||||
* scope annotated {@code @CheckHQL}, any static string argument to
|
||||
* any one of the following methods is interpreted as HQL/JPQL and
|
||||
* validated:
|
||||
* <ul>
|
||||
* <li>{@link jakarta.persistence.EntityManager#createQuery(String,Class)},
|
||||
* <li>{@link jakarta.persistence.EntityManager#createQuery(String)},
|
||||
* <li>{@link org.hibernate.Session#createSelectionQuery(String,Class)}, or
|
||||
* <li>{@link org.hibernate.Session#createMutationQuery(String)}
|
||||
* </ul>
|
||||
* or to any one of the annotation members:
|
||||
* <ul>
|
||||
* <li>{@link jakarta.persistence.NamedQuery#query},
|
||||
* <li>{@link org.hibernate.annotations.NamedQuery#query}, or
|
||||
* <li>{@link HQL#value}
|
||||
* </ul>
|
||||
* <p>
|
||||
* is interpreted as HQL/JPQL and validated. Errors in the query are
|
||||
* reported by the Java compiler.
|
||||
* <p>
|
||||
* The entity classes referred to in the queries must be annotated
|
||||
* The entity classes referred to by the queries must be annotated
|
||||
* with basic JPA metadata annotations like {@code @Entity},
|
||||
* {@code @ManyToOne}, {@code @Embeddable}, {@code @MappedSuperclass},
|
||||
* {@code @ElementCollection}, and {@code @Access}. Metadata specified
|
||||
|
@ -50,7 +53,6 @@ import static java.lang.annotation.RetentionPolicy.CLASS;
|
|||
* Syntax errors, unknown entity names and unknown entity member names,
|
||||
* and typing errors all result in compile-time errors.
|
||||
*
|
||||
* @see HQL#value()
|
||||
* @see jakarta.persistence.NamedQuery#query()
|
||||
* @see jakarta.persistence.EntityManager#createQuery(String,Class)
|
||||
* @see org.hibernate.Session#createSelectionQuery(String,Class)
|
||||
|
|
|
@ -52,6 +52,10 @@ import static java.lang.annotation.RetentionPolicy.CLASS;
|
|||
* <p>
|
||||
* The method parameters must match the parameters of the HQL query,
|
||||
* either by name or by position.
|
||||
* <p>
|
||||
* Queries specified using this annotation are always validated by
|
||||
* the Metamodel Generator, and so it isn't necessary to specify the
|
||||
* {@link CheckHQL} annotation.
|
||||
*
|
||||
* @author Gavin King
|
||||
* @since 6.3
|
||||
|
|
|
@ -14,6 +14,8 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
|
@ -241,6 +243,19 @@ public final class Context {
|
|||
return lazyXmlParsing;
|
||||
}
|
||||
|
||||
public void message(Element method, String message, Diagnostic.Kind severity) {
|
||||
getProcessingEnvironment().getMessager()
|
||||
.printMessage( severity, message, method );
|
||||
}
|
||||
public void message(Element method, AnnotationMirror mirror, String message, Diagnostic.Kind severity) {
|
||||
getProcessingEnvironment().getMessager()
|
||||
.printMessage( severity, message, method, mirror,
|
||||
mirror.getElementValues().entrySet().stream()
|
||||
.filter( entry -> entry.getKey().getSimpleName().contentEquals("query")
|
||||
|| entry.getKey().getSimpleName().contentEquals("value") )
|
||||
.map(Map.Entry::getValue).findAny().orElseThrow() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
|
|
@ -10,10 +10,14 @@ import org.hibernate.jpamodelgen.model.MetaAttribute;
|
|||
import org.hibernate.jpamodelgen.model.Metamodel;
|
||||
import org.hibernate.jpamodelgen.util.Constants;
|
||||
import org.hibernate.jpamodelgen.util.TypeUtils;
|
||||
import org.hibernate.jpamodelgen.validation.ProcessorSessionFactory;
|
||||
import org.hibernate.jpamodelgen.validation.Validation;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
|
||||
public abstract class AnnotationMeta implements Metamodel {
|
||||
|
||||
void addAuxiliaryMembers() {
|
||||
|
@ -36,6 +40,54 @@ public abstract class AnnotationMeta implements Metamodel {
|
|||
addAuxiliaryMembersForRepeatableAnnotation( Constants.HIB_FILTER_DEFS, "FILTER_" );
|
||||
}
|
||||
|
||||
void checkNamedQueries() {
|
||||
if ( TypeUtils.containsAnnotation( getElement(), Constants.CHECK_HQL )
|
||||
|| TypeUtils.containsAnnotation( getElement().getEnclosingElement(), Constants.CHECK_HQL ) ) {
|
||||
checkNamedQueriesForAnnotation( Constants.NAMED_QUERY );
|
||||
checkNamedQueriesForRepeatableAnnotation( Constants.NAMED_QUERIES );
|
||||
checkNamedQueriesForAnnotation( Constants.HIB_NAMED_QUERY );
|
||||
checkNamedQueriesForRepeatableAnnotation( Constants.HIB_NAMED_QUERIES );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void checkNamedQueriesForAnnotation(String annotationName) {
|
||||
AnnotationMirror mirror = TypeUtils.getAnnotationMirror(getElement(), annotationName);
|
||||
if ( mirror != null ) {
|
||||
checkNamedQueriesForMirror( mirror );
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNamedQueriesForRepeatableAnnotation(String annotationName) {
|
||||
AnnotationMirror mirror = TypeUtils.getAnnotationMirror(getElement(), annotationName);
|
||||
if ( mirror != null ) {
|
||||
mirror.getElementValues().forEach((key, value) -> {
|
||||
if ( key.getSimpleName().contentEquals("value") ) {
|
||||
List<? extends AnnotationMirror> values =
|
||||
(List<? extends AnnotationMirror>) value.getValue();
|
||||
for ( AnnotationMirror annotationMirror : values ) {
|
||||
checkNamedQueriesForMirror( annotationMirror );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNamedQueriesForMirror(AnnotationMirror mirror) {
|
||||
mirror.getElementValues().forEach((key, value) -> {
|
||||
if ( key.getSimpleName().contentEquals("query") ) {
|
||||
String hql = value.getValue().toString();
|
||||
Validation.validate(
|
||||
hql,
|
||||
false,
|
||||
emptySet(), emptySet(),
|
||||
new ErrorHandler( getElement(), mirror, hql, getContext()),
|
||||
ProcessorSessionFactory.create( getContext().getProcessingEnvironment() )
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addAuxiliaryMembersForRepeatableAnnotation(String annotationName, String prefix) {
|
||||
AnnotationMirror mirror = TypeUtils.getAnnotationMirror( getElement(), annotationName );
|
||||
if ( mirror != null ) {
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
package org.hibernate.jpamodelgen.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -24,11 +23,6 @@ import javax.lang.model.type.TypeMirror;
|
|||
import javax.lang.model.util.ElementFilter;
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
import org.antlr.v4.runtime.atn.ATNConfigSet;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.hibernate.jpamodelgen.Context;
|
||||
import org.hibernate.jpamodelgen.ImportContextImpl;
|
||||
|
@ -49,7 +43,6 @@ import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation;
|
|||
import static org.hibernate.jpamodelgen.util.TypeUtils.determineAnnotationSpecifiedAccessType;
|
||||
import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationMirror;
|
||||
import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationValue;
|
||||
import static org.hibernate.query.hql.internal.StandardHqlTranslator.prettifyAntlrError;
|
||||
|
||||
/**
|
||||
* Class used to collect meta information about an annotated type (entity, embeddable or mapped superclass).
|
||||
|
@ -231,6 +224,8 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
|
||||
addAuxiliaryMembers();
|
||||
|
||||
checkNamedQueries();
|
||||
|
||||
addQueryMethods( queryMethods );
|
||||
|
||||
initialized = true;
|
||||
|
@ -320,11 +315,11 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
addQueryMethod( method, methodName, returnTypeName, containerTypeName );
|
||||
}
|
||||
else {
|
||||
displayError( method, "incorrect return type '" + containerTypeName + "'" );
|
||||
context.message( method, "incorrect return type '" + containerTypeName + "'", Diagnostic.Kind.ERROR );
|
||||
}
|
||||
}
|
||||
else {
|
||||
displayError( method, "incorrect return type '" + declaredType + "'" );
|
||||
context.message( method, "incorrect return type '" + declaredType + "'", Diagnostic.Kind.ERROR );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -397,7 +392,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
hql,
|
||||
false,
|
||||
emptySet(), emptySet(),
|
||||
new ErrorHandler( method, mirror, hql ),
|
||||
new ErrorHandler( method, mirror, hql, context),
|
||||
ProcessorSessionFactory.create( context.getProcessingEnvironment() )
|
||||
);
|
||||
}
|
||||
|
@ -408,8 +403,8 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
for (int i = 1; i <= paramNames.size(); i++) {
|
||||
final String param = paramNames.get(i-1);
|
||||
if ( !hql.contains(":" + param) && !hql.contains("?" + i) ) {
|
||||
displayError( method, mirror, "missing query parameter for '" + param
|
||||
+ "' (no parameter named :" + param + " or ?" + i + ")" );
|
||||
context.message( method, mirror, "missing query parameter for '" + param
|
||||
+ "' (no parameter named :" + param + " or ?" + i + ")", Diagnostic.Kind.ERROR );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -440,62 +435,5 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
// }
|
||||
// }
|
||||
|
||||
private void displayError(Element method, String message) {
|
||||
context.getProcessingEnvironment().getMessager()
|
||||
.printMessage( Diagnostic.Kind.ERROR, message, method );
|
||||
}
|
||||
private void displayError(Element method, AnnotationMirror mirror, String message) {
|
||||
context.getProcessingEnvironment().getMessager()
|
||||
.printMessage( Diagnostic.Kind.ERROR, message, method, mirror,
|
||||
mirror.getElementValues().entrySet().stream()
|
||||
.filter( entry -> entry.getKey().getSimpleName().toString().equals("value") )
|
||||
.map(Map.Entry::getValue).findAny().orElseThrow() );
|
||||
}
|
||||
|
||||
private class ErrorHandler implements Validation.Handler {
|
||||
private final ExecutableElement method;
|
||||
private final AnnotationMirror mirror;
|
||||
private final String queryString;
|
||||
private int errorcount;
|
||||
|
||||
public ErrorHandler(ExecutableElement method, AnnotationMirror mirror, String queryString) {
|
||||
this.method = method;
|
||||
this.mirror = mirror;
|
||||
this.queryString = queryString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int start, int end, String message) {
|
||||
errorcount++;
|
||||
displayError( method, mirror, message );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(int start, int end, String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getErrorCount() {
|
||||
return errorcount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String message, RecognitionException e) {
|
||||
errorcount++;
|
||||
displayError( method, mirror, "illegal HQL syntax - "
|
||||
+ prettifyAntlrError( offendingSymbol, line, charPositionInLine, message, e, queryString, false ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAmbiguity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, boolean exact, BitSet ambigAlts, ATNConfigSet configs) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAttemptingFullContext(Parser recognizer, DFA dfa, int startIndex, int stopIndex, BitSet conflictingAlts, ATNConfigSet configs) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportContextSensitivity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, int prediction, ATNConfigSet configs) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,6 +118,8 @@ public class AnnotationMetaPackage extends AnnotationMeta {
|
|||
|
||||
addAuxiliaryMembers();
|
||||
|
||||
checkNamedQueries();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.jpamodelgen.annotation;
|
||||
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
import org.antlr.v4.runtime.atn.ATNConfigSet;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.hibernate.jpamodelgen.Context;
|
||||
import org.hibernate.jpamodelgen.validation.Validation;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.util.BitSet;
|
||||
|
||||
import static org.hibernate.query.hql.internal.StandardHqlTranslator.prettifyAntlrError;
|
||||
|
||||
/**
|
||||
* @author Gavin King
|
||||
*/
|
||||
class ErrorHandler implements Validation.Handler {
|
||||
private final Element element;
|
||||
private final AnnotationMirror mirror;
|
||||
private final String queryString;
|
||||
private final Context context;
|
||||
private int errorCount;
|
||||
|
||||
public ErrorHandler(Element element, AnnotationMirror mirror, String queryString, Context context) {
|
||||
this.element = element;
|
||||
this.mirror = mirror;
|
||||
this.queryString = queryString;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getErrorCount() {
|
||||
return errorCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int start, int end, String message) {
|
||||
errorCount++;
|
||||
context.message( element, mirror, message, Diagnostic.Kind.ERROR );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(int start, int end, String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String message, RecognitionException e) {
|
||||
errorCount++;
|
||||
String prettyMessage = "illegal HQL syntax - "
|
||||
+ prettifyAntlrError( offendingSymbol, line, charPositionInLine, message, e, queryString, false );
|
||||
context.message( element, mirror, prettyMessage, Diagnostic.Kind.ERROR );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAmbiguity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, boolean exact, BitSet ambigAlts, ATNConfigSet configs) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAttemptingFullContext(Parser recognizer, DFA dfa, int startIndex, int stopIndex, BitSet conflictingAlts, ATNConfigSet configs) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportContextSensitivity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, int prediction, ATNConfigSet configs) {
|
||||
}
|
||||
}
|
|
@ -53,6 +53,8 @@ public final class Constants {
|
|||
public static final String HQL = "org.hibernate.annotations.processing.HQL";
|
||||
public static final String SQL = "org.hibernate.annotations.processing.SQL";
|
||||
|
||||
public static final String CHECK_HQL = "org.hibernate.annotations.processing.CheckHQL";
|
||||
|
||||
public static final Map<String, String> COLLECTIONS = allCollectionTypes();
|
||||
|
||||
private static Map<String, String> allCollectionTypes() {
|
||||
|
|
Loading…
Reference in New Issue