From db4d529f603bc952faa894fc06bb4045fb728b65 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sun, 9 Jul 2023 20:37:59 +0200 Subject: [PATCH] HHH-16633 generate query methods from @NamedQuery annotations --- .../hibernate/query/sqm/SqmExpressible.java | 2 +- .../type/descriptor/java/JavaType.java | 7 + .../org/hibernate/jpamodelgen/Context.java | 20 +- .../JPAMetaModelEntityProcessor.java | 3 +- .../annotation/AbstractFinderMethod.java | 312 +++++++++--------- .../annotation/AnnotationMeta.java | 152 ++++++--- .../annotation/AnnotationMetaEntity.java | 80 +++-- .../annotation/AnnotationMetaPackage.java | 5 + .../jpamodelgen/annotation/ErrorHandler.java | 10 +- .../annotation/NameMetaAttribute.java | 9 +- .../annotation/NamedQueryMethod.java | 201 +++++++++++ .../hibernate/jpamodelgen/util/TypeUtils.java | 12 + .../validation/ProcessorSessionFactory.java | 30 +- .../jpamodelgen/validation/Validation.java | 26 +- .../test/namedquery/AuxiliaryTest.java | 54 ++- .../jpamodelgen/test/namedquery/Book.java | 14 + .../jpamodelgen/test/namedquery/Main.java | 4 +- .../jpamodelgen/test/namedquery/Type.java | 3 + .../jpamodelgen/test/util/TestUtil.java | 34 +- 19 files changed, 717 insertions(+), 261 deletions(-) create mode 100644 tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/NamedQueryMethod.java create mode 100644 tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Type.java diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmExpressible.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmExpressible.java index 118e21510a..31df20d6e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmExpressible.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmExpressible.java @@ -50,7 +50,7 @@ public interface SqmExpressible extends BindableType { default String getTypeName() { // default impl to handle the general case returning the Java type name JavaType expressibleJavaType = getExpressibleJavaType(); - return expressibleJavaType == null ? "unknown" : expressibleJavaType.getJavaType().getTypeName(); + return expressibleJavaType == null ? "unknown" : expressibleJavaType.getTypeName(); } DomainType getSqmType(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java index cf719d83a3..526681e4c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java @@ -86,6 +86,13 @@ public interface JavaType extends Serializable { return ReflectHelper.getClass( getJavaType() ); } + /** + * Get the name of the Java type. + */ + default String getTypeName() { + return getJavaType().getTypeName(); + } + /** * Is the given value an instance of the described type? *

diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/Context.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/Context.java index b0e649c47e..244efde0b6 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/Context.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/Context.java @@ -16,6 +16,7 @@ import java.util.Map; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; @@ -73,6 +74,9 @@ public final class Context { // keep track of all classes for which model have been generated private final Collection generatedModelClasses = new HashSet<>(); + // keep track of which named queries have been checked + private Set checkedNamedQueries = new HashSet<>(); + public Context(ProcessingEnvironment processingEnvironment) { this.processingEnvironment = processingEnvironment; @@ -266,13 +270,15 @@ public final class Context { getProcessingEnvironment().getMessager() .printMessage( severity, message, method ); } + + public void message(Element method, AnnotationMirror mirror, AnnotationValue value, String message, Diagnostic.Kind severity) { + getProcessingEnvironment().getMessager() + .printMessage( severity, message, method, mirror, value ); + } + 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() ); + .printMessage( severity, message, method, mirror ); } @Override @@ -288,4 +294,8 @@ public final class Context { sb.append( '}' ); return sb.toString(); } + + public boolean checkNamedQuery(String name) { + return checkedNamedQueries.add(name); + } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/JPAMetaModelEntityProcessor.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/JPAMetaModelEntityProcessor.java index 7d44742f96..22b247d643 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/JPAMetaModelEntityProcessor.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/JPAMetaModelEntityProcessor.java @@ -164,12 +164,13 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor { ); } else { + context.logMessage( Diagnostic.Kind.OTHER, "Starting new round" ); try { processClasses( roundEnvironment ); createMetaModelClasses(); } catch (Exception e) { - context.logMessage( Diagnostic.Kind.ERROR, "Error generating JPA metamodel:" + e.getMessage() ); + context.logMessage( Diagnostic.Kind.ERROR, "Error generating JPA metamodel: " + e.getMessage() ); } } return ALLOW_OTHER_PROCESSORS_TO_CLAIM_ANNOTATIONS; diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AbstractFinderMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AbstractFinderMethod.java index cfc437cf8c..648ce67bc1 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AbstractFinderMethod.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AbstractFinderMethod.java @@ -20,179 +20,179 @@ import static org.hibernate.jpamodelgen.util.StringUtil.getUpperUnderscoreCaseFr * @author Gavin King */ public abstract class AbstractFinderMethod implements MetaAttribute { - final Metamodel annotationMetaEntity; - final String methodName; - final String entity; - final boolean belongsToDao; - final String sessionType; - final boolean usingEntityManager; - final List fetchProfiles; + final Metamodel annotationMetaEntity; + final String methodName; + final String entity; + final boolean belongsToDao; + final String sessionType; + final boolean usingEntityManager; + final List fetchProfiles; - final List paramNames; - final List paramTypes; + final List paramNames; + final List paramTypes; - public AbstractFinderMethod( - Metamodel annotationMetaEntity, - String methodName, - String entity, - boolean belongsToDao, - String sessionType, - List fetchProfiles, List paramNames, List paramTypes) { - this.annotationMetaEntity = annotationMetaEntity; - this.methodName = methodName; - this.entity = entity; - this.belongsToDao = belongsToDao; - this.sessionType = sessionType; - this.fetchProfiles = fetchProfiles; - this.paramNames = paramNames; - this.paramTypes = paramTypes; - usingEntityManager = Constants.ENTITY_MANAGER.equals(sessionType); - } + public AbstractFinderMethod( + Metamodel annotationMetaEntity, + String methodName, + String entity, + boolean belongsToDao, + String sessionType, + List fetchProfiles, List paramNames, List paramTypes) { + this.annotationMetaEntity = annotationMetaEntity; + this.methodName = methodName; + this.entity = entity; + this.belongsToDao = belongsToDao; + this.sessionType = sessionType; + this.fetchProfiles = fetchProfiles; + this.paramNames = paramNames; + this.paramTypes = paramTypes; + usingEntityManager = Constants.ENTITY_MANAGER.equals(sessionType); + } - @Override - public boolean hasTypedAttribute() { - return true; - } + @Override + public boolean hasTypedAttribute() { + return true; + } - @Override - public boolean hasStringAttribute() { - return false; - } + @Override + public boolean hasStringAttribute() { + return false; + } - @Override - public String getMetaType() { - throw new UnsupportedOperationException(); - } + @Override + public String getMetaType() { + throw new UnsupportedOperationException(); + } - @Override - public String getPropertyName() { - return methodName; - } + @Override + public String getPropertyName() { + return methodName; + } - @Override - public String getTypeDeclaration() { - return entity; - } + @Override + public String getTypeDeclaration() { + return entity; + } - @Override - public Metamodel getHostingEntity() { - return annotationMetaEntity; - } + @Override + public Metamodel getHostingEntity() { + return annotationMetaEntity; + } - @Override - public String getAttributeNameDeclarationString() { - return new StringBuilder() - .append("public static final String ") - .append(constantName()) - .append(" = \"!") - .append(annotationMetaEntity.getQualifiedName()) - .append('.') - .append(methodName) - .append("(") - .append(parameterList()) - .append(")") - .append("\";") - .toString(); - } + @Override + public String getAttributeNameDeclarationString() { + return new StringBuilder() + .append("public static final String ") + .append(constantName()) + .append(" = \"!") + .append(annotationMetaEntity.getQualifiedName()) + .append('.') + .append(methodName) + .append("(") + .append(parameterList()) + .append(")") + .append("\";") + .toString(); + } - private String parameterList() { - return paramTypes.stream() - .map(this::strip) - .map(annotationMetaEntity::importType) - .reduce((x, y) -> x + ',' + y) - .orElse(""); - } + private String parameterList() { + return paramTypes.stream() + .map(this::strip) + .map(annotationMetaEntity::importType) + .reduce((x, y) -> x + ',' + y) + .orElse(""); + } - private String strip(String type) { - int index = type.indexOf("<"); - String stripped = index > 0 ? type.substring(0, index) : type; - return type.endsWith("...") ? stripped + "..." : stripped; - } + private String strip(String type) { + int index = type.indexOf("<"); + String stripped = index > 0 ? type.substring(0, index) : type; + return type.endsWith("...") ? stripped + "..." : stripped; + } - String constantName() { - return getUpperUnderscoreCaseFromLowerCamelCase(methodName) + "_BY_" - + paramNames.stream() - .map(StringHelper::unqualify) - .map(name -> name.toUpperCase(Locale.ROOT)) - .reduce((x,y) -> x + "_AND_" + y) - .orElse(""); - } + String constantName() { + return getUpperUnderscoreCaseFromLowerCamelCase(methodName) + "_BY_" + + paramNames.stream() + .map(StringHelper::unqualify) + .map(name -> name.toUpperCase(Locale.ROOT)) + .reduce((x,y) -> x + "_AND_" + y) + .orElse(""); + } - void comment(StringBuilder declaration) { - declaration - .append("\n/**\n * @see ") - .append(annotationMetaEntity.getQualifiedName()) - .append("#") - .append(methodName) - .append("(") - .append(parameterList()) - .append(")") - .append("\n **/\n"); - } + void comment(StringBuilder declaration) { + declaration + .append("\n/**\n * @see ") + .append(annotationMetaEntity.getQualifiedName()) + .append("#") + .append(methodName) + .append("(") + .append(parameterList()) + .append(")") + .append("\n **/\n"); + } - void unwrapSession(StringBuilder declaration) { - if ( usingEntityManager ) { - declaration - .append(".unwrap(") - .append(annotationMetaEntity.importType(Constants.HIB_SESSION)) - .append(".class)\n\t\t\t"); - } - } + void unwrapSession(StringBuilder declaration) { + if ( usingEntityManager ) { + declaration + .append(".unwrap(") + .append(annotationMetaEntity.importType(Constants.HIB_SESSION)) + .append(".class)\n\t\t\t"); + } + } - void enableFetchProfile(StringBuilder declaration) { -// if ( !usingEntityManager ) { -// declaration -// .append("\n\t\t\t.enableFetchProfile(") -// .append(constantName()) -// .append(")"); -// } - // currently unused: no way to specify explicit fetch profiles - for ( String profile : fetchProfiles ) { - declaration - .append("\n\t\t\t.enableFetchProfile(") - .append(profile) - .append(")"); - } - } + void enableFetchProfile(StringBuilder declaration) { +// if ( !usingEntityManager ) { +// declaration +// .append("\n\t\t\t.enableFetchProfile(") +// .append(constantName()) +// .append(")"); +// } + // currently unused: no way to specify explicit fetch profiles + for ( String profile : fetchProfiles ) { + declaration + .append("\n\t\t\t.enableFetchProfile(") + .append(profile) + .append(")"); + } + } - void preamble(StringBuilder declaration) { - modifiers( declaration ); - declaration - .append(annotationMetaEntity.importType(entity)); - declaration - .append(" ") - .append(methodName); - parameters( declaration) ; - declaration - .append(" {") - .append("\n\treturn entityManager"); - } + void preamble(StringBuilder declaration) { + modifiers( declaration ); + declaration + .append(annotationMetaEntity.importType(entity)); + declaration + .append(" ") + .append(methodName); + parameters( declaration) ; + declaration + .append(" {") + .append("\n\treturn entityManager"); + } - void modifiers(StringBuilder declaration) { - declaration - .append(belongsToDao ? "@Override\npublic " : "public static "); - } + void modifiers(StringBuilder declaration) { + declaration + .append(belongsToDao ? "@Override\npublic " : "public static "); + } - void parameters(StringBuilder declaration) { - declaration - .append("("); - if ( !belongsToDao ) { - declaration - .append(annotationMetaEntity.importType(Constants.ENTITY_MANAGER)) - .append(" entityManager"); - } - for ( int i = 0; i < paramNames.size(); i ++ ) { - if ( !belongsToDao || i > 0 ) { - declaration - .append(", "); - } - declaration - .append(annotationMetaEntity.importType(paramTypes.get(i))) - .append(" ") - .append(paramNames.get(i)); - } - declaration - .append(')'); - } + void parameters(StringBuilder declaration) { + declaration + .append("("); + if ( !belongsToDao ) { + declaration + .append(annotationMetaEntity.importType(Constants.ENTITY_MANAGER)) + .append(" entityManager"); + } + for ( int i = 0; i < paramNames.size(); i ++ ) { + if ( !belongsToDao || i > 0 ) { + declaration + .append(", "); + } + declaration + .append(annotationMetaEntity.importType(paramTypes.get(i))) + .append(" ") + .append(paramNames.get(i)); + } + declaration + .append(')'); + } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java index 5ec3a3ca73..0bcb35f7ca 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java @@ -6,18 +6,22 @@ */ package org.hibernate.jpamodelgen.annotation; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; import org.hibernate.jpamodelgen.model.MetaAttribute; import org.hibernate.jpamodelgen.model.Metamodel; import org.hibernate.jpamodelgen.util.Constants; import org.hibernate.jpamodelgen.validation.ProcessorSessionFactory; import org.hibernate.jpamodelgen.validation.Validation; +import org.hibernate.query.sqm.tree.SqmStatement; +import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import java.util.List; import static java.util.Collections.emptySet; -import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation; -import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationMirror; +import static org.hibernate.jpamodelgen.util.TypeUtils.*; public abstract class AnnotationMeta implements Metamodel { @@ -44,61 +48,90 @@ public abstract class AnnotationMeta implements Metamodel { void checkNamedQueries() { boolean checkHql = containsAnnotation( getElement(), Constants.CHECK_HQL ) || containsAnnotation( getElement().getEnclosingElement(), Constants.CHECK_HQL ); - checkNamedQueriesForAnnotation( Constants.NAMED_QUERY, checkHql ); - checkNamedQueriesForRepeatableAnnotation( Constants.NAMED_QUERIES, checkHql ); - checkNamedQueriesForAnnotation( Constants.HIB_NAMED_QUERY, checkHql ); - checkNamedQueriesForRepeatableAnnotation( Constants.HIB_NAMED_QUERIES, checkHql ); + handleNamedQueryAnnotation( Constants.NAMED_QUERY, checkHql ); + handleNamedQueryRepeatableAnnotation( Constants.NAMED_QUERIES, checkHql ); + handleNamedQueryAnnotation( Constants.HIB_NAMED_QUERY, checkHql ); + handleNamedQueryRepeatableAnnotation( Constants.HIB_NAMED_QUERIES, checkHql ); } - private void checkNamedQueriesForAnnotation(String annotationName, boolean checkHql) { + private void handleNamedQueryAnnotation(String annotationName, boolean checkHql) { final AnnotationMirror mirror = getAnnotationMirror( getElement(), annotationName ); if ( mirror != null ) { - checkNamedQueriesForMirror( mirror, checkHql ); + handleNamedQuery( mirror, checkHql ); } } - private void checkNamedQueriesForRepeatableAnnotation(String annotationName, boolean checkHql) { + private void handleNamedQueryRepeatableAnnotation(String annotationName, boolean checkHql) { final AnnotationMirror mirror = getAnnotationMirror( getElement(), annotationName ); if ( mirror != null ) { - mirror.getElementValues().forEach((key, value) -> { - if ( key.getSimpleName().contentEquals("value") ) { - List values = - (List) value.getValue(); - for ( AnnotationMirror annotationMirror : values ) { - checkNamedQueriesForMirror( annotationMirror, checkHql ); + final Object value = getAnnotationValue( mirror, "value" ); + if ( value instanceof List ) { + @SuppressWarnings("unchecked") + final List values = + (List) value; + for ( AnnotationMirror annotationMirror : values ) { + handleNamedQuery( annotationMirror, checkHql ); + } + } + } + } + + private void handleNamedQuery(AnnotationMirror mirror, boolean checkHql) { + final Object nameValue = getAnnotationValue( mirror, "name" ); + if ( nameValue instanceof String ) { + final String name = nameValue.toString(); + final boolean reportErrors = getContext().checkNamedQuery( name ); + final AnnotationValue value = getAnnotationValueRef( mirror, "query" ); + if ( value != null ) { + final Object query = value.getValue(); + if ( query instanceof String ) { + final String hql = (String) query; + final SqmStatement statement = + Validation.validate( + hql, + false, true, + emptySet(), emptySet(), + // If we are in the scope of @CheckHQL, semantic errors in the + // query result in compilation errors. Otherwise, they only + // result in warnings, so we don't break working code. + new WarningErrorHandler( mirror, value, hql, reportErrors, checkHql ), + ProcessorSessionFactory.create( getContext().getProcessingEnvironment() ) + ); + if ( statement instanceof SqmSelectStatement + && isQueryMethodName( name ) ) { + putMember( name, + new NamedQueryMethod( + this, + (SqmSelectStatement) statement, + name.substring(1), + belongsToDao() + ) + ); } } - }); + } } } - private void checkNamedQueriesForMirror(AnnotationMirror mirror, boolean checkHql) { - mirror.getElementValues().forEach((key, value) -> { - if ( key.getSimpleName().contentEquals("query") ) { - final String hql = value.getValue().toString(); - Validation.validate( - hql, - false, checkHql, - emptySet(), emptySet(), - new ErrorHandler( getElement(), mirror, hql, getContext() ), - ProcessorSessionFactory.create( getContext().getProcessingEnvironment() ) - ); - } - }); + private static boolean isQueryMethodName(String name) { + return name.length() >= 2 + && name.charAt(0) == '#' + && Character.isJavaIdentifierStart( name.charAt(1) ) + && name.substring(2).chars().allMatch(Character::isJavaIdentifierPart); } private void addAuxiliaryMembersForRepeatableAnnotation(String annotationName, String prefix) { final AnnotationMirror mirror = getAnnotationMirror( getElement(), annotationName ); if ( mirror != null ) { - mirror.getElementValues().forEach((key, value) -> { - if ( key.getSimpleName().contentEquals("value") ) { - List values = - (List) value.getValue(); - for ( AnnotationMirror annotationMirror : values ) { - addAuxiliaryMembersForMirror( annotationMirror, prefix ); - } + final Object value = getAnnotationValue( mirror, "value" ); + if ( value instanceof List ) { + @SuppressWarnings("unchecked") + final List values = + (List) value; + for ( AnnotationMirror annotationMirror : values ) { + addAuxiliaryMembersForMirror( annotationMirror, prefix ); } - }); + } } } @@ -113,11 +146,52 @@ public abstract class AnnotationMeta implements Metamodel { mirror.getElementValues().forEach((key, value) -> { if ( key.getSimpleName().contentEquals("name") ) { final String name = value.getValue().toString(); - putMember( prefix + name, - new NameMetaAttribute( this, name, prefix ) ); + if ( !name.isEmpty() ) { + putMember( prefix + name, + new NameMetaAttribute( this, name, prefix ) ); + } } }); } + abstract boolean belongsToDao(); + abstract void putMember(String name, MetaAttribute nameMetaAttribute); + + private class WarningErrorHandler extends ErrorHandler { + private final boolean reportErrors; + private final boolean checkHql; + + public WarningErrorHandler(AnnotationMirror mirror, AnnotationValue value, String hql, boolean reportErrors, boolean checkHql) { + super( AnnotationMeta.this.getElement(), mirror, value, hql, AnnotationMeta.this.getContext() ); + this.reportErrors = reportErrors; + this.checkHql = checkHql; + } + + @Override + public void error(int start, int end, String message) { + if (reportErrors) { + if (checkHql) { + super.error( start, end, message ); + } + else { + super.warn( start, end, message ); + } + } + } + + @Override + public void warn(int start, int end, String message) { + if (reportErrors) { + super.warn( start, end, message ); + } + } + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String message, RecognitionException e) { + if (reportErrors) { + super.syntaxError( recognizer, offendingSymbol, line, charPositionInLine, message, e ); + } + } + } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java index 72966d3588..1be6cbcee3 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java @@ -47,6 +47,7 @@ import static org.hibernate.jpamodelgen.util.TypeUtils.determineAccessTypeForHie 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.jpamodelgen.util.TypeUtils.getAnnotationValueRef; /** * Class used to collect meta information about an annotated type (entity, embeddable or mapped superclass). @@ -201,6 +202,11 @@ public class AnnotationMetaEntity extends AnnotationMeta { members.put( name, nameMetaAttribute ); } + @Override + public boolean belongsToDao() { + return dao; + } + @Override public String toString() { return new StringBuilder() @@ -642,36 +648,39 @@ public class AnnotationMetaEntity extends AnnotationMeta { @Nullable TypeElement containerType, AnnotationMirror mirror, boolean isNative) { - final Object queryString = getAnnotationValue( mirror, "value" ); - if ( queryString instanceof String ) { - final List paramNames = parameterNames( method ); - final List paramTypes = parameterTypes( method ); - final String hql = (String) queryString; - final QueryMethod attribute = - new QueryMethod( - this, - method.getSimpleName().toString(), - hql, - returnType == null ? null : returnType.toString(), - containerType == null ? null : containerType.getQualifiedName().toString(), - paramNames, - paramTypes, - isNative, - dao, - sessionType - ); - putMember( attribute.getPropertyName() + paramTypes, attribute ); + final AnnotationValue value = getAnnotationValueRef( mirror, "value" ); + if ( value != null ) { + final Object query = value.getValue(); + if ( query instanceof String ) { + final String hql = (String) query; + final List paramNames = parameterNames( method ); + final List paramTypes = parameterTypes( method ); + final QueryMethod attribute = + new QueryMethod( + this, + method.getSimpleName().toString(), + hql, + returnType == null ? null : returnType.toString(), + containerType == null ? null : containerType.getQualifiedName().toString(), + paramNames, + paramTypes, + isNative, + dao, + sessionType + ); + putMember( attribute.getPropertyName() + paramTypes, attribute ); - checkParameters( method, paramNames, paramTypes, mirror, hql ); - if ( !isNative ) { -// checkHqlSyntax( method, mirror, hql ); - Validation.validate( - hql, - false, true, - emptySet(), emptySet(), - new ErrorHandler( method, mirror, hql, context ), - ProcessorSessionFactory.create( context.getProcessingEnvironment() ) - ); + checkParameters( method, paramNames, paramTypes, mirror, value, hql ); + if ( !isNative ) { + // checkHqlSyntax( method, mirror, hql ); + Validation.validate( + hql, + false, true, + emptySet(), emptySet(), + new ErrorHandler( method, mirror, value, hql, context ), + ProcessorSessionFactory.create( context.getProcessingEnvironment() ) + ); + } } } } @@ -688,13 +697,20 @@ public class AnnotationMetaEntity extends AnnotationMeta { .collect(toList()); } - private void checkParameters(ExecutableElement method, List paramNames, List paramTypes, AnnotationMirror mirror, String hql) { + private void checkParameters( + ExecutableElement method, + List paramNames, List paramTypes, + AnnotationMirror mirror, + AnnotationValue value, + String hql) { for (int i = 1; i <= paramNames.size(); i++) { final String param = paramNames.get(i-1); final String type = paramTypes.get(i-1); if ( parameterIsMissing( hql, i, param, type ) ) { - context.message( method, mirror, "missing query parameter for '" + param - + "' (no parameter named :" + param + " or ?" + i + ")", Diagnostic.Kind.ERROR ); + context.message( method, mirror, value, + "missing query parameter for '" + param + + "' (no parameter named :" + param + " or ?" + i + ")", + Diagnostic.Kind.ERROR ); } } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaPackage.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaPackage.java index 728ea6aebc..cf1a546ba4 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaPackage.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaPackage.java @@ -133,4 +133,9 @@ public class AnnotationMetaPackage extends AnnotationMeta { void putMember(String name, MetaAttribute nameMetaAttribute) { members.put( name, nameMetaAttribute ); } + + @Override + boolean belongsToDao() { + return false; + } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/ErrorHandler.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/ErrorHandler.java index f211ed34d4..cadeebaac5 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/ErrorHandler.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/ErrorHandler.java @@ -15,6 +15,7 @@ import org.hibernate.jpamodelgen.Context; import org.hibernate.jpamodelgen.validation.Validation; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.tools.Diagnostic; import java.util.BitSet; @@ -27,13 +28,15 @@ import static org.hibernate.query.hql.internal.StandardHqlTranslator.prettifyAnt class ErrorHandler implements Validation.Handler { private final Element element; private final AnnotationMirror mirror; + private final AnnotationValue value; private final String queryString; private final Context context; private int errorCount; - public ErrorHandler(Element element, AnnotationMirror mirror, String queryString, Context context) { + public ErrorHandler(Element element, AnnotationMirror mirror, AnnotationValue value, String queryString, Context context) { this.element = element; this.mirror = mirror; + this.value = value; this.queryString = queryString; this.context = context; } @@ -46,11 +49,12 @@ class ErrorHandler implements Validation.Handler { @Override public void error(int start, int end, String message) { errorCount++; - context.message( element, mirror, message, Diagnostic.Kind.ERROR ); + context.message( element, mirror, value, message, Diagnostic.Kind.ERROR ); } @Override public void warn(int start, int end, String message) { + context.message( element, mirror, value, message, Diagnostic.Kind.WARNING ); } @Override @@ -58,7 +62,7 @@ class ErrorHandler implements Validation.Handler { errorCount++; String prettyMessage = "illegal HQL syntax - " + prettifyAntlrError( offendingSymbol, line, charPositionInLine, message, e, queryString, false ); - context.message( element, mirror, prettyMessage, Diagnostic.Kind.ERROR ); + context.message( element, mirror, value, prettyMessage, Diagnostic.Kind.ERROR ); } @Override diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/NameMetaAttribute.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/NameMetaAttribute.java index a22ec6d039..49d6b9ed19 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/NameMetaAttribute.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/NameMetaAttribute.java @@ -8,7 +8,8 @@ package org.hibernate.jpamodelgen.annotation; import org.hibernate.jpamodelgen.model.MetaAttribute; import org.hibernate.jpamodelgen.model.Metamodel; -import org.hibernate.jpamodelgen.util.StringUtil; + +import static org.hibernate.jpamodelgen.util.StringUtil.nameToFieldName; /** * @author Gavin King @@ -46,7 +47,7 @@ class NameMetaAttribute implements MetaAttribute { .append(annotationMetaEntity.importType(String.class.getName())) .append(" ") .append(prefix) - .append(StringUtil.nameToFieldName(name)) + .append(fieldName()) .append(" = ") .append("\"") .append(name) @@ -55,6 +56,10 @@ class NameMetaAttribute implements MetaAttribute { .toString(); } + private String fieldName() { + return nameToFieldName(name.charAt(0) == '#' ? name.substring(1) : name); + } + @Override public String getMetaType() { throw new UnsupportedOperationException(); diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/NamedQueryMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/NamedQueryMethod.java new file mode 100644 index 0000000000..37bea3615b --- /dev/null +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/NamedQueryMethod.java @@ -0,0 +1,201 @@ +/* + * 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 . + */ +package org.hibernate.jpamodelgen.annotation; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.jpamodelgen.model.MetaAttribute; +import org.hibernate.jpamodelgen.model.Metamodel; +import org.hibernate.jpamodelgen.util.Constants; +import org.hibernate.query.sqm.SqmExpressible; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.query.sqm.tree.select.SqmSelectStatement; +import org.hibernate.query.sqm.tree.select.SqmSelectableNode; +import org.hibernate.type.descriptor.java.JavaType; + +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.TypeElement; +import java.util.List; +import java.util.TreeSet; + +import static org.hibernate.jpamodelgen.util.StringUtil.nameToFieldName; +import static org.hibernate.jpamodelgen.validation.ProcessorSessionFactory.findEntityByUnqualifiedName; + +/** + * @author Gavin King + */ +class NamedQueryMethod implements MetaAttribute { + private final AnnotationMeta annotationMeta; + private final SqmSelectStatement select; + private final String name; + private final boolean belongsToDao; + + public NamedQueryMethod(AnnotationMeta annotationMeta, SqmSelectStatement select, String name, boolean belongsToDao) { + this.annotationMeta = annotationMeta; + this.select = select; + this.name = name; + this.belongsToDao = belongsToDao; + } + + @Override + public boolean hasTypedAttribute() { + return true; + } + + @Override + public boolean hasStringAttribute() { + return false; + } + + @Override + public String getAttributeDeclarationString() { + final TreeSet> sortedParameters = + new TreeSet<>( select.getSqmParameters() ); + final String returnType = returnType(); + StringBuilder declaration = new StringBuilder(); + comment( declaration ); + modifiers( declaration ); + returnType( returnType, declaration ); + parameters( sortedParameters, declaration ); + declaration + .append(" {") + .append("\n\treturn entityManager.createNamedQuery(") + .append(fieldName()) + .append(")"); + for ( SqmParameter param : sortedParameters ) { + declaration + .append("\n\t\t\t.setParameter(") + .append(param.getName() == null ? param.getPosition() : '"' + param.getName() + '"') + .append(", ") + .append(param.getName() == null ? "parameter" + param.getPosition() : param.getName()) + .append(')'); + } + declaration + .append("\n\t\t\t.getResultList();\n}"); + return declaration.toString(); + } + + private String fieldName() { + return "QUERY_" + nameToFieldName(name); + } + + private String returnType() { + final JavaType javaType = select.getSelection().getJavaTypeDescriptor(); + if ( javaType != null ) { + return javaType.getTypeName(); + } + else { + final List> items = + select.getQuerySpec().getSelectClause().getSelectionItems(); + if ( items.size() == 1 ) { + final String typeName = items.get(0).getExpressible().getTypeName(); + final TypeElement entityType = entityType( typeName ); + return entityType == null ? typeName : entityType.getQualifiedName().toString(); + + } + else { + return "Object[]"; + } + } + } + + private void comment(StringBuilder declaration) { + declaration + .append("\n/**\n * Executes named query {@value #") + .append(fieldName()) + .append("} defined by annotation of {@link ") + .append(annotationMeta.getSimpleName()) + .append("}.\n **/\n"); + } + + private void modifiers(StringBuilder declaration) { + declaration + .append(belongsToDao ? "public " : "public static "); + } + + private void returnType(String returnType, StringBuilder declaration) { + declaration + .append(annotationMeta.importType(Constants.LIST)) + .append('<') + .append(annotationMeta.importType(returnType)) + .append("> ") + .append(name); + } + + private void parameters(TreeSet> sortedParameters, StringBuilder declaration) { + declaration + .append('('); + if ( !belongsToDao ) { + declaration + .append(annotationMeta.importType(Constants.ENTITY_MANAGER)) + .append(" entityManager"); + } + int i = 0; + for ( SqmParameter param : sortedParameters) { + if ( 0 < i++ || !belongsToDao ) { + declaration + .append(", "); + } + declaration + .append(parameterType(param)) + .append(" ") + .append(parameterName(param)); + } + declaration + .append(')'); + } + + private static String parameterName(SqmParameter param) { + return param.getName() == null ? "parameter" + param.getPosition() : param.getName(); + } + + private String parameterType(SqmParameter param) { + final SqmExpressible expressible = param.getExpressible(); + final String paramType = expressible == null ? "unknown" : expressible.getTypeName(); //getTypeName() can return "unknown" + return "unknown".equals(paramType) ? "Object" : annotationMeta.importType(paramType); + } + + private @Nullable TypeElement entityType(String entityName) { + TypeElement symbol = + findEntityByUnqualifiedName( entityName, + annotationMeta.getContext().getElementUtils().getModuleElement("") ); + if ( symbol != null ) { + return symbol; + } + for ( ModuleElement module : annotationMeta.getContext().getElementUtils().getAllModuleElements() ) { + symbol = findEntityByUnqualifiedName( entityName, module ); + if ( symbol != null ) { + return symbol; + } + } + return null; + } + + @Override + public String getAttributeNameDeclarationString() { + throw new UnsupportedOperationException(); + } + + @Override + public String getMetaType() { + throw new UnsupportedOperationException(); + } + + @Override + public String getPropertyName() { + return name; + } + + @Override + public String getTypeDeclaration() { + return Constants.LIST; + } + + @Override + public Metamodel getHostingEntity() { + return annotationMeta; + } +} diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/util/TypeUtils.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/util/TypeUtils.java index aaf747345e..8f75bd2398 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/util/TypeUtils.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/util/TypeUtils.java @@ -221,6 +221,18 @@ public final class TypeUtils { return null; } + public static @Nullable AnnotationValue getAnnotationValueRef(AnnotationMirror annotationMirror, String parameterValue) { + assert annotationMirror != null; + assert parameterValue != null; + for ( Map.Entry entry + : annotationMirror.getElementValues().entrySet() ) { + if ( entry.getKey().getSimpleName().contentEquals( parameterValue ) ) { + return entry.getValue(); + } + } + return null; + } + public static void determineAccessTypeForHierarchy(TypeElement searchedElement, Context context) { final String fqcn = searchedElement.getQualifiedName().toString(); context.logMessage( Diagnostic.Kind.OTHER, "Determining access type for " + fqcn ); diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/validation/ProcessorSessionFactory.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/validation/ProcessorSessionFactory.java index 2838002e61..21eb30c55e 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/validation/ProcessorSessionFactory.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/validation/ProcessorSessionFactory.java @@ -142,13 +142,12 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory { } static Type propertyType(Element member, String entityName, String path, AccessType defaultAccessType) { - TypeMirror memberType = memberType(member); + final TypeMirror memberType = memberType(member); if (isEmbeddedProperty(member)) { return component.make(asElement(memberType), entityName, path, defaultAccessType); } else if (isToOneAssociation(member)) { - String targetEntity = getToOneTargetEntity(member); - return new ManyToOneType(typeConfiguration, targetEntity); + return new ManyToOneType(typeConfiguration, getToOneTargetEntity(member)); } else if (isToManyAssociation(member)) { return collectionType(memberType, qualify(entityName, path)); @@ -157,7 +156,7 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory { return collectionType(memberType, qualify(entityName, path)); } else if (isEnumProperty(member)) { - return new BasicTypeImpl(new EnumJavaType(Enum.class), enumJdbcType(member)); + return enumType( member, memberType ); } else { return typeConfiguration.getBasicTypeRegistry() @@ -165,6 +164,27 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory { } } + @SuppressWarnings({"rawtypes", "unchecked"}) + private static BasicType enumType(Element member, TypeMirror memberType) { + final Class enumClass = Enum.class; // because we can't load the real enum class! + return enumType( member, qualifiedName( memberType ), enumClass ); + } + + private static > BasicType enumType(Element member, String typeName, Class enumClass) { + final EnumJavaType javaType = new EnumJavaType<>( enumClass ) { + @Override + public String getTypeName() { + return typeName; + } + }; + return new BasicTypeImpl<>( javaType, enumJdbcType(member) ) { + @Override + public String getTypeName() { + return typeName; + } + }; + } + private static JdbcType enumJdbcType(Element member) { VariableElement mapping = (VariableElement) getAnnotationMember(getAnnotation(member,"Enumerated"), "value"); @@ -395,7 +415,7 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory { return null; } - private static TypeElement findEntityByUnqualifiedName(String entityName, ModuleElement module) { + public static TypeElement findEntityByUnqualifiedName(String entityName, ModuleElement module) { for (Element element: module.getEnclosedElements()) { if (element.getKind() == ElementKind.PACKAGE) { PackageElement pack = (PackageElement) element; diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/validation/Validation.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/validation/Validation.java index 238cc4ebab..3e4fa0bdc2 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/validation/Validation.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/validation/Validation.java @@ -12,6 +12,7 @@ import org.antlr.v4.runtime.DefaultErrorStrategy; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.atn.PredictionMode; import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.PropertyNotFoundException; import org.hibernate.QueryException; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -22,6 +23,7 @@ import org.hibernate.query.hql.internal.SemanticQueryBuilder; import org.hibernate.query.sqm.EntityTypeException; import org.hibernate.query.sqm.PathElementException; import org.hibernate.query.sqm.TerminalPathException; +import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException; import java.util.ArrayList; @@ -46,17 +48,17 @@ public class Validation { int getErrorCount(); } - public static void validate( + public static @Nullable SqmStatement validate( String hql, boolean checkParams, boolean checkTyping, Set setParameterLabels, Set setParameterNames, Handler handler, SessionFactoryImplementor factory) { - validate( hql, checkParams, checkTyping, setParameterLabels, setParameterNames, handler, factory, 0 ); + return validate( hql, checkParams, checkTyping, setParameterLabels, setParameterNames, handler, factory, 0 ); } - public static void validate( + public static @Nullable SqmStatement validate( String hql, boolean checkParams, boolean checkTyping, Set setParameterLabels, @@ -69,25 +71,28 @@ public class Validation { try { final HqlParser.StatementContext statementContext = parseAndCheckSyntax( hql, handler ); if ( checkTyping && handler.getErrorCount() == 0 ) { - checkTyping( hql, handler, factory, errorOffset, statementContext ); - } - if ( checkParams ) { - checkParameterBinding( hql, setParameterLabels, setParameterNames, handler, errorOffset ); + final SqmStatement statement = + checkTyping( hql, handler, factory, errorOffset, statementContext ); + if ( checkParams ) { + checkParameterBinding( hql, setParameterLabels, setParameterNames, handler, errorOffset ); + } + return statement; } } catch (Exception e) { // e.printStackTrace(); } + return null; } - private static void checkTyping( + private static @Nullable SqmStatement checkTyping( String hql, Handler handler, SessionFactoryImplementor factory, int errorOffset, HqlParser.StatementContext statementContext) { try { - new SemanticQueryBuilder<>( Object[].class, () -> false, factory ) + return new SemanticQueryBuilder<>( Object[].class, () -> false, factory ) .visitStatement( statementContext ); } catch ( JdbcTypeRecommendationException ignored ) { @@ -95,11 +100,12 @@ public class Validation { } catch ( QueryException | PathElementException | TerminalPathException | EntityTypeException | PropertyNotFoundException se ) { //TODO is this one really thrown by core? It should not be! - String message = se.getMessage(); + final String message = se.getMessage(); if ( message != null ) { handler.error( -errorOffset +1, -errorOffset + hql.length(), message ); } } + return null; } private static HqlParser.StatementContext parseAndCheckSyntax(String hql, Handler handler) { diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/AuxiliaryTest.java b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/AuxiliaryTest.java index 7640fab670..97090a868d 100644 --- a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/AuxiliaryTest.java +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/AuxiliaryTest.java @@ -6,12 +6,14 @@ */ package org.hibernate.jpamodelgen.test.namedquery; +import jakarta.persistence.EntityManager; import org.hibernate.jpamodelgen.test.util.CompilationTest; import org.hibernate.jpamodelgen.test.util.TestUtil; import org.hibernate.jpamodelgen.test.util.WithClasses; import org.junit.Test; import static org.hibernate.jpamodelgen.test.util.TestUtil.assertMetamodelClassGeneratedFor; +import static org.hibernate.jpamodelgen.test.util.TestUtil.assertPresenceOfMethodInMetamodelFor; import static org.hibernate.jpamodelgen.test.util.TestUtil.assertPresenceOfNameFieldInMetamodelFor; /** @@ -20,8 +22,9 @@ import static org.hibernate.jpamodelgen.test.util.TestUtil.assertPresenceOfNameF public class AuxiliaryTest extends CompilationTest { @Test @WithClasses({ Book.class, Main.class }) - public void testGeneratedAnnotationNotGenerated() { + public void test() { System.out.println( TestUtil.getMetaModelSourceAsString( Main.class ) ); + System.out.println( TestUtil.getMetaModelSourceAsString( Book.class ) ); assertMetamodelClassGeneratedFor( Book.class ); assertMetamodelClassGeneratedFor( Main.class ); assertPresenceOfNameFieldInMetamodelFor( @@ -74,10 +77,59 @@ public class AuxiliaryTest extends CompilationTest { "QUERY__SYSDATE_", "Missing fetch profile attribute." ); + assertPresenceOfMethodInMetamodelFor( + Main.class, + "bookByIsbn", + EntityManager.class, + String.class + ); + assertPresenceOfMethodInMetamodelFor( + Main.class, + "bookByTitle", + EntityManager.class, + String.class + ); + assertPresenceOfNameFieldInMetamodelFor( Book.class, "GRAPH_ENTITY_GRAPH", "Missing fetch profile attribute." ); + assertPresenceOfMethodInMetamodelFor( + Book.class, + "findByTitle", + EntityManager.class, + String.class + ); + assertPresenceOfMethodInMetamodelFor( + Book.class, + "findByTitleAndType", + EntityManager.class, + String.class, + Type.class + ); + assertPresenceOfMethodInMetamodelFor( + Book.class, + "getTitles", + EntityManager.class + ); + assertPresenceOfMethodInMetamodelFor( + Book.class, + "getUpperLowerTitles", + EntityManager.class + ); + assertPresenceOfMethodInMetamodelFor( + Book.class, + "typeOfBook", + EntityManager.class, + String.class + ); + assertPresenceOfMethodInMetamodelFor( + Book.class, + "crazy", + EntityManager.class, + Object.class, + Object.class + ); } } diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Book.java b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Book.java index b7b8838034..4e60ec7dac 100644 --- a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Book.java +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Book.java @@ -3,11 +3,25 @@ package org.hibernate.jpamodelgen.test.namedquery; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.NamedQuery; @Entity @NamedEntityGraph(name = "entityGraph") +@NamedQuery(name = "#findByTitle", + query = "from Book where title like :titlePattern") +@NamedQuery(name = "#findByTitleAndType", + query = "select book from Book book where book.title like :titlePattern and book.type = :type") +@NamedQuery(name = "#getTitles", + query = "select title from Book") +@NamedQuery(name = "#getUpperLowerTitles", + query = "select upper(title), lower(title), length(title) from Book") +@NamedQuery(name = "#typeOfBook", + query = "select type from Book where isbn = :isbn") +@NamedQuery(name = "#crazy", + query = "select 1 where :x = :y") public class Book { @Id String isbn; String title; String text; + Type type = Type.Book; } diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Main.java b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Main.java index 19347714ee..a9ae152e82 100644 --- a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Main.java +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Main.java @@ -9,8 +9,8 @@ import jakarta.persistence.SqlResultSetMappings; import org.hibernate.annotations.FetchProfile; import org.hibernate.annotations.FetchProfiles; -@NamedQueries(@NamedQuery(name = "bookByIsbn", query = "from Book where isbn = :isbn")) -@NamedQuery(name = "bookByTitle", query = "from Book where title = :title") +@NamedQueries(@NamedQuery(name = "#bookByIsbn", query = "from Book where isbn = :isbn")) +@NamedQuery(name = "#bookByTitle", query = "from Book where title = :title") @FetchProfile(name = "dummy-fetch") @FetchProfiles({@FetchProfile(name = "fetch.one"), @FetchProfile(name = "fetch#two")}) @NamedNativeQuery(name = "bookNativeQuery", query = "select * from Book") diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Type.java b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Type.java new file mode 100644 index 0000000000..3086a051f1 --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Type.java @@ -0,0 +1,3 @@ +package org.hibernate.jpamodelgen.test.namedquery; + +enum Type {Book, Magazine, Journal} diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/util/TestUtil.java b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/util/TestUtil.java index 87525795b1..a255e5046f 100644 --- a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/util/TestUtil.java +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/util/TestUtil.java @@ -13,6 +13,7 @@ import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.URL; @@ -78,10 +79,23 @@ public class TestUtil { ); } + public static void assertPresenceOfMethodInMetamodelFor(Class clazz, String methodName, Class... params) { + assertPresenceOfMethodInMetamodelFor( + clazz, + methodName, + "'" + methodName + "' should appear in metamodel class", + params + ); + } + public static void assertPresenceOfFieldInMetamodelFor(Class clazz, String fieldName, String errorString) { assertTrue( buildErrorString( errorString, clazz ), hasFieldInMetamodelFor( clazz, fieldName ) ); } + public static void assertPresenceOfMethodInMetamodelFor(Class clazz, String fieldName, String errorString, Class... params) { + assertTrue( buildErrorString( errorString, clazz ), hasMethodInMetamodelFor( clazz, fieldName, params ) ); + } + public static void assertPresenceOfNameFieldInMetamodelFor(Class clazz, String fieldName, String errorString) { assertTrue( buildErrorString( errorString, clazz ), hasFieldInMetamodelFor( clazz, fieldName ) ); assertEquals(buildErrorString(errorString, clazz), getFieldFromMetamodelFor(clazz, fieldName).getType(), String.class); @@ -254,14 +268,22 @@ public class TestUtil { public static Field getFieldFromMetamodelFor(Class entityClass, String fieldName) { Class metaModelClass = getMetamodelClassFor( entityClass ); - Field field; try { - field = metaModelClass.getDeclaredField( fieldName ); + return metaModelClass.getDeclaredField( fieldName ); } catch ( NoSuchFieldException e ) { - field = null; + return null; + } + } + + public static Method getMethodFromMetamodelFor(Class entityClass, String methodName, Class... params) { + Class metaModelClass = getMetamodelClassFor( entityClass ); + try { + return metaModelClass.getDeclaredMethod( methodName, params ); + } + catch ( NoSuchMethodException e ) { + return null; } - return field; } public static String fcnToPath(String fcn) { @@ -272,6 +294,10 @@ public class TestUtil { return getFieldFromMetamodelFor( clazz, fieldName ) != null; } + private static boolean hasMethodInMetamodelFor(Class clazz, String fieldName, Class... params) { + return getMethodFromMetamodelFor( clazz, fieldName, params ) != null; + } + private static String buildErrorString(String baseError, Class clazz) { StringBuilder builder = new StringBuilder(); builder.append( baseError );