From f0c9d4ec4cf902a5a09f35acade24b97a3c69445 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Thu, 28 Mar 2024 16:04:44 +0100 Subject: [PATCH] make fully-qualified enum literals in @Query pass the validation JD examples and TCK require this, though it's not really correct Signed-off-by: Gavin King --- .../metamodel/model/domain/JpaMetamodel.java | 5 ++ .../domain/internal/JpaMetamodelImpl.java | 22 +++++ .../domain/internal/MappingMetamodelImpl.java | 11 +++ .../internal/BasicDotIdentifierConsumer.java | 80 ++++++++----------- .../test/data/{ => basic}/Author.java | 2 +- .../processor/test/data/{ => basic}/Book.java | 2 +- .../{ => basic}/BookAuthorRepository.java | 3 +- .../test/data/{ => basic}/DataTest.java | 2 +- .../processor/test/data/eg/Library.java | 3 + .../validation/MockSessionFactory.java | 23 ++++++ .../validation/ProcessorSessionFactory.java | 6 ++ 11 files changed, 107 insertions(+), 52 deletions(-) rename tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/{ => basic}/Author.java (85%) rename tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/{ => basic}/Book.java (92%) rename tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/{ => basic}/BookAuthorRepository.java (98%) rename tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/{ => basic}/DataTest.java (96%) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java index 12a676d7c8..d28c1873f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/JpaMetamodel.java @@ -19,6 +19,7 @@ import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.service.ServiceRegistry; +import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.spi.TypeConfiguration; /** @@ -85,6 +86,10 @@ public interface JpaMetamodel extends Metamodel { */ Map, Enum>> getAllowedEnumLiteralTexts(); + EnumJavaType getEnumType(String prefix); + + > E enumValue(EnumJavaType enumType, String terminal); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Covariant returns diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java index 51fdf02c6c..4800674942 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java @@ -243,6 +243,28 @@ public class JpaMetamodelImpl implements JpaMetamodelImplementor, Serializable { return allowedEnumLiteralTexts; } + @Override + public EnumJavaType getEnumType(String prefix) { + try { + final Class namedClass = + getServiceRegistry().requireService( ClassLoaderService.class ) + .classForName( prefix ); + if ( namedClass != null && namedClass.isEnum() ) { + return (EnumJavaType) getTypeConfiguration() + .getJavaTypeRegistry() + .resolveDescriptor(namedClass); + } + } + catch (Exception ignore) { + } + return null; + } + + @Override + public > E enumValue(EnumJavaType enumType, String terminal) { + return Enum.valueOf( enumType.getJavaTypeClass(), terminal ); + } + @Override public void addNamedEntityGraph(String graphName, RootGraphImplementor entityGraph) { final EntityGraph old = entityGraphMap.put( diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java index b992e2028d..ec14f230a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java @@ -72,6 +72,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.type.BasicType; import org.hibernate.type.ComponentType; import org.hibernate.type.Type; +import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.jdbc.JdbcType; @@ -523,6 +524,16 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl return jpaMetamodel.getAllowedEnumLiteralTexts(); } + @Override + public EnumJavaType getEnumType(String className) { + return jpaMetamodel.getEnumType(className); + } + + @Override + public > E enumValue(EnumJavaType enumType, String terminal) { + return jpaMetamodel.enumValue(enumType, terminal); + } + @Override public String[] getImplementors(String className) throws MappingException { // computeIfAbsent() can be a contention point and we expect all the values to be in the map at some point so diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java index 8ab4b88be0..4b95e34f69 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/BasicDotIdentifierConsumer.java @@ -10,12 +10,14 @@ import java.lang.reflect.Field; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; import org.hibernate.query.SemanticException; import org.hibernate.query.hql.HqlLogging; import org.hibernate.query.hql.spi.DotIdentifierConsumer; import org.hibernate.query.hql.spi.SemanticPathPart; import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.spi.SqmCreationContext; import org.hibernate.query.sqm.tree.domain.SqmPath; @@ -26,7 +28,6 @@ import org.hibernate.query.sqm.tree.expression.SqmLiteralEntityType; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; /** * @asciidoc @@ -177,17 +178,20 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer { } final String path = pathSoFar.toString(); - final String importableName = creationContext.getJpaMetamodel().qualifyImportableName( path ); + final JpaMetamodelImplementor jpaMetamodel = creationContext.getJpaMetamodel(); + final String importableName = jpaMetamodel.qualifyImportableName( path ); + final NodeBuilder nodeBuilder = creationContext.getNodeBuilder(); if ( importableName != null ) { - final EntityDomainType entityDomainType = creationContext.getJpaMetamodel().entity( importableName ); + final EntityDomainType entityDomainType = jpaMetamodel.entity( importableName ); if ( entityDomainType != null ) { - return new SqmLiteralEntityType( entityDomainType, creationContext.getNodeBuilder() ); + return new SqmLiteralEntityType( entityDomainType, nodeBuilder ); } } - final SqmFunctionDescriptor functionDescriptor = creationContext.getQueryEngine() - .getSqmFunctionRegistry() - .findFunctionDescriptor( path ); + final SqmFunctionDescriptor functionDescriptor = + creationContext.getQueryEngine() + .getSqmFunctionRegistry() + .findFunctionDescriptor( path ); if ( functionDescriptor != null ) { return functionDescriptor.generateSqmExpression( null, @@ -195,55 +199,37 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer { ); } -// // see if it is a Class name... -// try { -// final Class namedClass = creationState.getCreationContext() -// .getServiceRegistry() -// .getService( ClassLoaderService.class ) -// .classForName( pathSoFar ); -// if ( namedClass != null ) { -// return new -// } -// } -// catch (Exception ignore) { -// } - // see if it is a named field/enum reference final int splitPosition = path.lastIndexOf( '.' ); if ( splitPosition > 0 ) { final String prefix = path.substring( 0, splitPosition ); final String terminal = path.substring( splitPosition + 1 ); + //TODO: try interpreting paths of form foo.bar.Foo.Bar as foo.bar.Foo$Bar + final EnumJavaType enumType = jpaMetamodel.getEnumType(prefix); + if ( enumType != null ) { + return new SqmEnumLiteral( + jpaMetamodel.enumValue(enumType, terminal), + enumType, + terminal, + nodeBuilder + ); + } try { - final Class namedClass = creationContext - .getServiceRegistry() - .requireService( ClassLoaderService.class ) - .classForName( prefix ); + final Class namedClass = + creationContext.getServiceRegistry() + .requireService( ClassLoaderService.class ) + .classForName( prefix ); if ( namedClass != null ) { - final JavaTypeRegistry javaTypeRegistry = creationContext.getJpaMetamodel() - .getTypeConfiguration() - .getJavaTypeRegistry(); - - if ( namedClass.isEnum() ) { - return new SqmEnumLiteral( - Enum.valueOf( (Class) namedClass, terminal ), - (EnumJavaType) javaTypeRegistry.resolveDescriptor( namedClass ), - terminal, - creationContext.getNodeBuilder() - ); - } - - try { - final Field referencedField = namedClass.getDeclaredField( terminal ); - if ( referencedField != null ) { - final JavaType fieldJtd = javaTypeRegistry - .getDescriptor( referencedField.getType() ); - //noinspection unchecked - return new SqmFieldLiteral( referencedField, fieldJtd, creationContext.getNodeBuilder() ); - } - } - catch (Exception ignore) { + final Field referencedField = namedClass.getDeclaredField( terminal ); + if ( referencedField != null ) { + final JavaType fieldJtd = + jpaMetamodel + .getTypeConfiguration() + .getJavaTypeRegistry() + .getDescriptor( referencedField.getType() ); + return new SqmFieldLiteral( referencedField, fieldJtd, nodeBuilder); } } } diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/Author.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/Author.java similarity index 85% rename from tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/Author.java rename to tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/Author.java index 6ec531176a..f7c4320bc9 100644 --- a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/Author.java +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/Author.java @@ -1,4 +1,4 @@ -package org.hibernate.processor.test.data; +package org.hibernate.processor.test.data.basic; import jakarta.persistence.Entity; import jakarta.persistence.Id; diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/Book.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/Book.java similarity index 92% rename from tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/Book.java rename to tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/Book.java index 580278909e..f42428dc23 100644 --- a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/Book.java +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/Book.java @@ -1,4 +1,4 @@ -package org.hibernate.processor.test.data; +package org.hibernate.processor.test.data.basic; import jakarta.persistence.*; import org.hibernate.annotations.NaturalId; diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/BookAuthorRepository.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/BookAuthorRepository.java similarity index 98% rename from tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/BookAuthorRepository.java rename to tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/BookAuthorRepository.java index bcb18cb6e6..f8c7876bd4 100644 --- a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/BookAuthorRepository.java +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/BookAuthorRepository.java @@ -1,4 +1,4 @@ -package org.hibernate.processor.test.data; +package org.hibernate.processor.test.data.basic; import jakarta.data.Limit; import jakarta.data.Order; @@ -22,7 +22,6 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.stream.Stream; @Repository(dataStore = "myds") diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/DataTest.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/DataTest.java similarity index 96% rename from tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/DataTest.java rename to tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/DataTest.java index 96309a5998..8234aa4b11 100644 --- a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/DataTest.java +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/basic/DataTest.java @@ -4,7 +4,7 @@ * 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.processor.test.data; +package org.hibernate.processor.test.data.basic; import org.hibernate.processor.test.util.CompilationTest; import org.hibernate.processor.test.util.WithClasses; diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/eg/Library.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/eg/Library.java index cc4ced1555..22a32e3fd8 100644 --- a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/eg/Library.java +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/eg/Library.java @@ -102,4 +102,7 @@ public interface Library { @Find List authorsByCityAndPostcode(String address_city, String address_postcode); + + @Query("where type = org.hibernate.processor.test.data.eg.Type.Magazine") + List magazines(); } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java index c63293bf5c..9e5bcb3cd4 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/MockSessionFactory.java @@ -108,6 +108,7 @@ import org.hibernate.type.MapType; import org.hibernate.type.SetType; import org.hibernate.type.SqlTypes; import org.hibernate.type.Type; +import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; @@ -283,6 +284,8 @@ public abstract class MockSessionFactory abstract boolean isClassDefined(String qualifiedName); + abstract boolean isEnum(String className); + abstract boolean isFieldDefined(String qualifiedClassName, String fieldName); abstract boolean isConstructorDefined(String qualifiedClassName, List argumentTypes); @@ -823,6 +826,26 @@ public abstract class MockSessionFactory throw new UnsupportedOperationException("operation not supported"); } + @Override + public EnumJavaType getEnumType(String className) { + if ( isEnum(className) ) { + return new EnumJavaType( Enum.class ) { + @Override + public String getTypeName() { + return className; + } + }; + } + else { + return null; + } + } + + @Override + public > E enumValue(EnumJavaType enumType, String terminal) { + return null; + } + @Override public JpaCompliance getJpaCompliance() { return jpaCompliance; diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java index eaaa9ca25f..892cc1db28 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/validation/ProcessorSessionFactory.java @@ -563,6 +563,12 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory { } } + @Override + boolean isEnum(String className) { + final TypeElement typeElement = elementUtil.getTypeElement(className); + return typeElement != null && typeElement.getKind() == ElementKind.ENUM; + } + private static boolean isEmbeddableType(TypeElement type) { return hasAnnotation(type, "Embeddable"); }