HHH-16633 generate query methods from @NamedQuery annotations

This commit is contained in:
Gavin King 2023-07-09 20:37:59 +02:00
parent 16b433ebf1
commit db4d529f60
19 changed files with 717 additions and 261 deletions

View File

@ -50,7 +50,7 @@ public interface SqmExpressible<J> extends BindableType<J> {
default String getTypeName() {
// default impl to handle the general case returning the Java type name
JavaType<J> expressibleJavaType = getExpressibleJavaType();
return expressibleJavaType == null ? "unknown" : expressibleJavaType.getJavaType().getTypeName();
return expressibleJavaType == null ? "unknown" : expressibleJavaType.getTypeName();
}
DomainType<J> getSqmType();

View File

@ -86,6 +86,13 @@ public interface JavaType<T> 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?
* <p>

View File

@ -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<String> generatedModelClasses = new HashSet<>();
// keep track of which named queries have been checked
private Set<String> 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);
}
}

View File

@ -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;

View File

@ -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<? extends AnnotationMirror> values =
(List<? extends AnnotationMirror>) value.getValue();
final Object value = getAnnotationValue( mirror, "value" );
if ( value instanceof List ) {
@SuppressWarnings("unchecked")
final List<? extends AnnotationMirror> values =
(List<? extends AnnotationMirror>) value;
for ( AnnotationMirror annotationMirror : values ) {
checkNamedQueriesForMirror( annotationMirror, checkHql );
handleNamedQuery( annotationMirror, checkHql );
}
}
});
}
}
private void checkNamedQueriesForMirror(AnnotationMirror mirror, boolean checkHql) {
mirror.getElementValues().forEach((key, value) -> {
if ( key.getSimpleName().contentEquals("query") ) {
final String hql = value.getValue().toString();
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, checkHql,
false, true,
emptySet(), emptySet(),
new ErrorHandler( getElement(), mirror, hql, getContext() ),
// 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 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<? extends AnnotationMirror> values =
(List<? extends AnnotationMirror>) value.getValue();
final Object value = getAnnotationValue( mirror, "value" );
if ( value instanceof List ) {
@SuppressWarnings("unchecked")
final List<? extends AnnotationMirror> values =
(List<? extends AnnotationMirror>) 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();
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 );
}
}
}
}

View File

@ -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,11 +648,13 @@ public class AnnotationMetaEntity extends AnnotationMeta {
@Nullable TypeElement containerType,
AnnotationMirror mirror,
boolean isNative) {
final Object queryString = getAnnotationValue( mirror, "value" );
if ( queryString instanceof String ) {
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<String> paramNames = parameterNames( method );
final List<String> paramTypes = parameterTypes( method );
final String hql = (String) queryString;
final QueryMethod attribute =
new QueryMethod(
this,
@ -662,19 +670,20 @@ public class AnnotationMetaEntity extends AnnotationMeta {
);
putMember( attribute.getPropertyName() + paramTypes, attribute );
checkParameters( method, paramNames, paramTypes, mirror, hql );
checkParameters( method, paramNames, paramTypes, mirror, value, hql );
if ( !isNative ) {
// checkHqlSyntax( method, mirror, hql );
// checkHqlSyntax( method, mirror, hql );
Validation.validate(
hql,
false, true,
emptySet(), emptySet(),
new ErrorHandler( method, mirror, hql, context ),
new ErrorHandler( method, mirror, value, hql, context ),
ProcessorSessionFactory.create( context.getProcessingEnvironment() )
);
}
}
}
}
private static List<String> parameterTypes(ExecutableElement method) {
return method.getParameters().stream()
@ -688,13 +697,20 @@ public class AnnotationMetaEntity extends AnnotationMeta {
.collect(toList());
}
private void checkParameters(ExecutableElement method, List<String> paramNames, List<String> paramTypes, AnnotationMirror mirror, String hql) {
private void checkParameters(
ExecutableElement method,
List<String> paramNames, List<String> 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 );
}
}
}

View File

@ -133,4 +133,9 @@ public class AnnotationMetaPackage extends AnnotationMeta {
void putMember(String name, MetaAttribute nameMetaAttribute) {
members.put( name, nameMetaAttribute );
}
@Override
boolean belongsToDao() {
return false;
}
}

View File

@ -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

View File

@ -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();

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<SqmParameter<?>> 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<SqmSelectableNode<?>> 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<SqmParameter<?>> 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;
}
}

View File

@ -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<? extends ExecutableElement, ? extends AnnotationValue> 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 );

View File

@ -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<Enum> enumClass = Enum.class; // because we can't load the real enum class!
return enumType( member, qualifiedName( memberType ), enumClass );
}
private static <T extends Enum<T>> BasicType<T> enumType(Element member, String typeName, Class<T> enumClass) {
final EnumJavaType<T> 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;

View File

@ -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<Integer> setParameterLabels,
Set<String> 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<Integer> setParameterLabels,
@ -69,25 +71,28 @@ public class Validation {
try {
final HqlParser.StatementContext statementContext = parseAndCheckSyntax( hql, handler );
if ( checkTyping && handler.getErrorCount() == 0 ) {
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) {

View File

@ -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
);
}
}

View File

@ -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;
}

View File

@ -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")

View File

@ -0,0 +1,3 @@
package org.hibernate.jpamodelgen.test.namedquery;
enum Type {Book, Magazine, Journal}

View File

@ -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 );