From a6ffa29dce3706834fc73f7d50856b1692d43f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Wed, 25 Jun 2025 15:22:26 +0200 Subject: [PATCH 1/9] [HHH-19586] Support Panache2 - Detect Panache2 in classpath - Detect Panache2 types for entities and repositories --- .../java/org/hibernate/processor/Context.java | 18 +++++ .../processor/HibernateProcessor.java | 9 +++ .../annotation/AnnotationMetaEntity.java | 68 ++++++++++++++++--- .../hibernate/processor/util/Constants.java | 6 ++ .../ormPanache/QuarkusOrmPanacheTest.java | 3 +- 5 files changed, 92 insertions(+), 12 deletions(-) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/Context.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/Context.java index 9ef6a41e3f01..e83d982cb639 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/Context.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/Context.java @@ -102,6 +102,8 @@ public final class Context { private boolean usesQuarkusOrm = false; private boolean usesQuarkusReactive = false; + private boolean usesQuarkusPanache2 = false; + private boolean usesQuarkusReactiveCommon = false; private String[] includes = {"*"}; private String[] excludes = {}; @@ -443,6 +445,22 @@ public boolean usesQuarkusReactive() { return usesQuarkusReactive; } + public void setUsesQuarkusPanache2(boolean b) { + usesQuarkusPanache2 = b; + } + + public boolean usesQuarkusPanache2() { + return usesQuarkusPanache2; + } + + public void setUsesQuarkusReactiveCommon(boolean b) { + usesQuarkusReactiveCommon = b; + } + + public boolean usesQuarkusReactiveCommon() { + return usesQuarkusReactiveCommon; + } + public void setInclude(String include) { includes = include.split(",\\s*"); } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java index da6364466851..c1af5d2977e1 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java @@ -253,9 +253,16 @@ private boolean handleSettings(ProcessingEnvironment environment) { PackageElement quarkusOrmPanachePackage = context.getProcessingEnvironment().getElementUtils() .getPackageElement( "io.quarkus.hibernate.orm.panache" ); + PackageElement quarkusPanache2Package = + context.getProcessingEnvironment().getElementUtils() + .getPackageElement( "io.quarkus.hibernate.panache" ); PackageElement quarkusReactivePanachePackage = context.getProcessingEnvironment().getElementUtils() .getPackageElement( "io.quarkus.hibernate.reactive.panache" ); + // This is imported automatically by Quarkus extensions when HR is also imported + PackageElement quarkusReactivePanacheCommonPackage = + context.getProcessingEnvironment().getElementUtils() + .getPackageElement( "io.quarkus.hibernate.reactive.panache.common" ); if ( packagePresent(quarkusReactivePanachePackage) && packagePresent(quarkusOrmPanachePackage) ) { @@ -275,6 +282,8 @@ && packagePresent(quarkusOrmPanachePackage) ) { context.setQuarkusInjection( packagePresent(quarkusOrmPackage) || packagePresent(quarkusReactivePackage) ); context.setUsesQuarkusOrm( packagePresent(quarkusOrmPanachePackage) ); context.setUsesQuarkusReactive( packagePresent(quarkusReactivePanachePackage) ); + context.setUsesQuarkusPanache2( packagePresent(quarkusPanache2Package) ); + context.setUsesQuarkusReactiveCommon( packagePresent(quarkusReactivePanacheCommonPackage) ); final Map options = environment.getOptions(); diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java index 8773d5b6a2f7..d7b2c6787296 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java @@ -764,21 +764,26 @@ private void setupSession() { final ExecutableElement getter = findSessionGetter( element ); if ( getter != null ) { // Never make a DAO for Panache subtypes - if ( !isPanacheType( element ) ) { + if ( !isPanacheType( element ) && !isPanache2Type( element ) ) { repository = true; sessionType = addDaoConstructor( getter ); } - else { - // For Panache subtypes, we look at the session type, but no DAO, we want static methods + else if ( ! isPanache2Repository( element ) && !isPanache2Type( element ) ) { + // For Panache 1 subtypes, we look at the session type, but no DAO, we want static methods sessionType = fullReturnType(getter); } + else { + // For Panache 2 repositories we want a repository + repository = true; + sessionType = setupQuarkusDaoConstructor( getter, element ); + } } else if ( element.getKind() == ElementKind.INTERFACE && !jakartaDataRepository - && ( context.usesQuarkusOrm() || context.usesQuarkusReactive() ) ) { + && ( context.usesQuarkusOrm() || context.usesQuarkusReactive() || context.usesQuarkusPanache2() ) ) { // if we don't have a getter, and not a JD repository, but we're in Quarkus, we know how to find the default sessions repository = true; - sessionType = setupQuarkusDaoConstructor(); + sessionType = setupQuarkusDaoConstructor( null, element ); } if ( !repository && jakartaDataRepository ) { repository = true; @@ -878,6 +883,19 @@ private boolean isReactivePanacheType(TypeElement type) { || extendsClass( type, PANACHE_REACTIVE_ENTITY_BASE ); } + private boolean isPanache2Type(TypeElement type) { + return implementsInterface( type, PANACHE2_ENTITY_MARKER ) + || isPanache2Repository( type ); + } + + private boolean isPanache2Repository(TypeElement type) { + return implementsInterface( type, PANACHE2_MANAGED_BLOCKING_REPOSITORY_BASE ) + || implementsInterface( type, PANACHE2_STATELESS_BLOCKING_REPOSITORY_BASE ) + || implementsInterface( type, PANACHE2_MANAGED_REACTIVE_REPOSITORY_BASE ) + || implementsInterface( type, PANACHE2_STATELESS_REACTIVE_REPOSITORY_BASE ) + ; + } + /** * If there is a session getter method, we generate an instance * variable backing it, together with a constructor that initializes @@ -915,10 +933,31 @@ private String addDaoConstructor(@Nullable ExecutableElement method) { /** * For Quarkus, we generate a constructor with injection for EntityManager in ORM, * and in HR, we define the static session getter. + * For Panache 2, we can use the element to figure out what kind of session we want since this + * is for repositories */ - private String setupQuarkusDaoConstructor() { - if ( context.usesQuarkusOrm() ) { - String name = "getEntityManager"; + private String setupQuarkusDaoConstructor(@Nullable ExecutableElement getter, @Nullable TypeElement element) { + if ( context.usesQuarkusOrm() + || (context.usesQuarkusPanache2() + && element != null + && (implementsInterface(element, PANACHE2_MANAGED_BLOCKING_REPOSITORY_BASE) + || implementsInterface(element, PANACHE2_STATELESS_BLOCKING_REPOSITORY_BASE))) + ) { + String name; + String sessionType; + if ( getter != null ) { + name = getter.getSimpleName().toString(); + sessionType = fullReturnType(getter); + } + else if(element != null + && implementsInterface(element, PANACHE2_STATELESS_BLOCKING_REPOSITORY_BASE)) { + name = "getStatelessSession"; + sessionType = HIB_STATELESS_SESSION; + } + else { // good default + name = "getSession"; + sessionType = HIB_SESSION; + } putMember( name, new RepositoryConstructor( this, @@ -934,13 +973,20 @@ private String setupQuarkusDaoConstructor() { true ) ); - return ENTITY_MANAGER; + return sessionType; } else { importType( Constants.QUARKUS_SESSION_OPERATIONS ); // use this getter to get the method, do not generate an injection point for its type - sessionGetter = "SessionOperations.getSession()"; - return Constants.UNI_MUTINY_SESSION; + if(element != null + && implementsInterface(element, PANACHE2_STATELESS_REACTIVE_REPOSITORY_BASE)) { + sessionGetter = "SessionOperations.getStatelessSession()"; + return UNI_MUTINY_STATELESS_SESSION; + } + else { + sessionGetter = "SessionOperations.getSession()"; + return Constants.UNI_MUTINY_SESSION; + } } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/Constants.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/Constants.java index c521d28a9d62..7bb3793da03b 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/Constants.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/Constants.java @@ -158,6 +158,12 @@ public final class Constants { public static final String PANACHE_REACTIVE_REPOSITORY_BASE = "io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase"; public static final String PANACHE_REACTIVE_ENTITY_BASE = "io.quarkus.hibernate.reactive.panache.PanacheEntityBase"; + public static final String PANACHE2_ENTITY_MARKER = "io.quarkus.hibernate.panache.PanacheEntityMarker"; + public static final String PANACHE2_MANAGED_BLOCKING_REPOSITORY_BASE = "io.quarkus.hibernate.panache.managed.blocking.PanacheManagedBlockingRepositoryBase"; + public static final String PANACHE2_STATELESS_BLOCKING_REPOSITORY_BASE = "io.quarkus.hibernate.panache.stateless.blocking.PanacheStatelessBlockingRepositoryBase"; + public static final String PANACHE2_MANAGED_REACTIVE_REPOSITORY_BASE = "io.quarkus.hibernate.panache.managed.reactive.PanacheManagedReactiveRepositoryBase"; + public static final String PANACHE2_STATELESS_REACTIVE_REPOSITORY_BASE = "io.quarkus.hibernate.panache.stateless.reactive.PanacheStatelessReactiveRepositoryBase"; + public static final Map COLLECTIONS = Map.of( COLLECTION, Constants.COLLECTION_ATTRIBUTE, SET, Constants.SET_ATTRIBUTE, diff --git a/tooling/metamodel-generator/src/quarkusOrmPanache/java/org/hibernate/processor/test/ormPanache/QuarkusOrmPanacheTest.java b/tooling/metamodel-generator/src/quarkusOrmPanache/java/org/hibernate/processor/test/ormPanache/QuarkusOrmPanacheTest.java index ccf3fe5bae0c..1798b595fe77 100644 --- a/tooling/metamodel-generator/src/quarkusOrmPanache/java/org/hibernate/processor/test/ormPanache/QuarkusOrmPanacheTest.java +++ b/tooling/metamodel-generator/src/quarkusOrmPanache/java/org/hibernate/processor/test/ormPanache/QuarkusOrmPanacheTest.java @@ -4,6 +4,7 @@ */ package org.hibernate.processor.test.ormPanache; +import org.hibernate.Session; import org.hibernate.processor.test.util.CompilationTest; import org.hibernate.processor.test.util.TestUtil; import org.hibernate.processor.test.util.WithClasses; @@ -100,7 +101,7 @@ void testQuarkusRepositoryMetamodel() throws Exception { Assertions.assertFalse( Modifier.isStatic( method.getModifiers() ) ); // Make sure we have the proper constructor - Constructor constructor = repositoryClass.getDeclaredConstructor( EntityManager.class ); + Constructor constructor = repositoryClass.getDeclaredConstructor( Session.class ); Assertions.assertNotNull( constructor ); Assertions.assertTrue( constructor.isAnnotationPresent( Inject.class ) ); } From 5999ca0be489b1f23137507966fe41ff3bfb3edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Wed, 25 Jun 2025 15:27:28 +0200 Subject: [PATCH 2/9] [HHH-19586] For Panache2, allow repositories to have a primary entity auto-detected Since repositories can be nested in entities, we can use the outer type --- .../processor/HibernateProcessor.java | 4 ++-- .../annotation/AnnotationMetaEntity.java | 20 ++++++++++++++----- .../annotation/NonManagedMetamodel.java | 2 +- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java index c1af5d2977e1..d6b185508051 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java @@ -662,7 +662,7 @@ private void handleRootElementAnnotationMirrors(final Element element, @Nullable final AnnotationMetaEntity metaEntity = AnnotationMetaEntity.create( typeElement, context, requiresLazyMemberInitialization, - true, false, parentMetaEntity ); + true, false, parentMetaEntity, typeElement ); if ( alreadyExistingMetaEntity != null ) { metaEntity.mergeInMembers( alreadyExistingMetaEntity ); } @@ -681,7 +681,7 @@ && hasAnnotation( element, ENTITY, MAPPED_SUPERCLASS ) final AnnotationMetaEntity dataMetaEntity = AnnotationMetaEntity.create( typeElement, context, requiresLazyMemberInitialization, - true, true, parentDataEntity ); + true, true, parentDataEntity, typeElement ); // final Metamodel alreadyExistingDataMetaEntity = // tryGettingExistingDataEntityFromContext( mirror, '_' + qualifiedName ); // if ( alreadyExistingDataMetaEntity != null ) { diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java index d7b2c6787296..797b6834364a 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java @@ -174,7 +174,8 @@ public class AnnotationMetaEntity extends AnnotationMeta { public AnnotationMetaEntity( TypeElement element, Context context, boolean managed, boolean jakartaDataStaticMetamodel, - @Nullable AnnotationMeta parent) { + @Nullable AnnotationMeta parent, + @Nullable TypeElement primaryEntity) { this.element = element; this.context = context; this.managed = managed; @@ -182,6 +183,7 @@ public AnnotationMetaEntity( this.quarkusInjection = context.isQuarkusInjection(); this.importContext = parent != null ? parent : new ImportContextImpl( getPackageName( context, element ) ); jakartaDataStaticModel = jakartaDataStaticMetamodel; + this.primaryEntity = primaryEntity; importContext.importType( getGeneratedClassFullyQualifiedName( element, getPackageName( context, element ), jakartaDataStaticModel ) ); @@ -190,17 +192,23 @@ public AnnotationMetaEntity( } } + public static AnnotationMetaEntity create(TypeElement element, Context context, @Nullable AnnotationMetaEntity parent, + @Nullable TypeElement primaryEntity) { + return create( element,context, false, false, false, parent, primaryEntity ); + } + public static AnnotationMetaEntity create(TypeElement element, Context context, @Nullable AnnotationMetaEntity parent) { - return create( element,context, false, false, false, parent ); + return create( element,context, false, false, false, parent, null ); } public static AnnotationMetaEntity create( TypeElement element, Context context, boolean lazilyInitialised, boolean managed, boolean jakartaData, - @Nullable AnnotationMetaEntity parent) { + @Nullable AnnotationMetaEntity parent, + @Nullable TypeElement primaryEntity) { final AnnotationMetaEntity annotationMetaEntity = - new AnnotationMetaEntity( element, context, managed, jakartaData, parent ); + new AnnotationMetaEntity( element, context, managed, jakartaData, parent, primaryEntity ); if ( parent != null ) { parent.addInnerClass( annotationMetaEntity ); } @@ -417,8 +425,10 @@ else if ( method.getEnclosingElement().getKind().isInterface() } } - primaryEntity = primaryEntity( lifecycleMethods ); final boolean hibernateRepo = isExplicitlyHibernateRepository(); + if ( primaryEntity == null ) { + primaryEntity = primaryEntity( lifecycleMethods ); + } if ( !checkEntity( primaryEntity, hibernateRepo ) || !checkEntities( lifecycleMethods, hibernateRepo ) ) { // NOTE EARLY EXIT with initialized = false diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NonManagedMetamodel.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NonManagedMetamodel.java index ce55cbcb4a9a..6127612d4022 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NonManagedMetamodel.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NonManagedMetamodel.java @@ -12,7 +12,7 @@ public class NonManagedMetamodel extends AnnotationMetaEntity { public NonManagedMetamodel(TypeElement element, Context context, boolean jakartaDataStaticMetamodel, @Nullable AnnotationMeta parent) { - super( element, context, false, jakartaDataStaticMetamodel, parent ); + super( element, context, false, jakartaDataStaticMetamodel, parent, null ); } public static NonManagedMetamodel create( From fc4aa907872afb9808e50460991d124014d41b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Wed, 25 Jun 2025 15:28:29 +0200 Subject: [PATCH 3/9] [HHH-19586] Detect repositories nested in entities, for Panache2 --- .../processor/HibernateProcessor.java | 147 ++++++++++-------- 1 file changed, 81 insertions(+), 66 deletions(-) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java index d6b185508051..c624a5795d11 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/HibernateProcessor.java @@ -398,72 +398,7 @@ private void processClasses(RoundEnvironment roundEnvironment) { private void processElement(Element element, @Nullable Element parent) { try { - if ( !included( element ) - || hasAnnotation( element, Constants.EXCLUDE ) - || hasPackageAnnotation( element, Constants.EXCLUDE ) - || element.getModifiers().contains( Modifier.PRIVATE ) ) { - // skip it completely - return; - } - else if ( isEntityOrEmbeddable( element ) - && !element.getModifiers().contains( Modifier.PRIVATE ) ) { - context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated entity class '" + element + "'" ); - handleRootElementAnnotationMirrors( element, parent ); - } - else if ( hasAuxiliaryAnnotations( element ) ) { - context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class '" + element + "'" ); - handleRootElementAuxiliaryAnnotationMirrors( element ); - } - else if ( element instanceof TypeElement typeElement ) { - final AnnotationMirror repository = getAnnotationMirror( element, JD_REPOSITORY ); - if ( repository != null ) { - final AnnotationValue provider = getAnnotationValue( repository, "provider" ); - if ( provider == null - || provider.getValue().toString().isEmpty() - || provider.getValue().toString().equalsIgnoreCase("hibernate") ) { - context.logMessage( Diagnostic.Kind.OTHER, "Processing repository class '" + element + "'" ); - final AnnotationMetaEntity metaEntity = - AnnotationMetaEntity.create( typeElement, context, - parentMetadata( parent, context::getMetaEntity ) ); - if ( metaEntity.isInitialized() ) { - context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity ); - } - // otherwise discard it (assume it has query by magical method name stuff) - } - } - else { - for ( Element member : typeElement.getEnclosedElements() ) { - if ( hasAnnotation( member, HQL, SQL, FIND ) ) { - context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class '" + element + "'" ); - final AnnotationMetaEntity metaEntity = - AnnotationMetaEntity.create( typeElement, context, - parentMetadata( parent, context::getMetaEntity ) ); - context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity ); - break; - } - } - if ( enclosesEntityOrEmbeddable( element ) ) { - final NonManagedMetamodel metaEntity = - NonManagedMetamodel.create( typeElement, context, false, - parentMetadata( parent, context::getMetamodel ) ); - context.addMetaEntity( metaEntity.getQualifiedName(), metaEntity ); - if ( context.generateJakartaDataStaticMetamodel() ) { - final NonManagedMetamodel dataMetaEntity = - NonManagedMetamodel.create( typeElement, context, true, - parentMetadata( parent, context::getDataMetaEntity ) ); - context.addDataMetaEntity( dataMetaEntity.getQualifiedName(), dataMetaEntity ); - } - - } - } - } - if ( isClassRecordOrInterfaceType( element ) ) { - for ( final Element child : element.getEnclosedElements() ) { - if ( isClassRecordOrInterfaceType( child ) ) { - processElement( child, element ); - } - } - } + inspectRootElement(element, parent, null); } catch ( ProcessLaterException processLaterException ) { if ( element instanceof TypeElement typeElement ) { @@ -493,6 +428,77 @@ private boolean hasPackageAnnotation(Element element, String annotation) { return pack != null && hasAnnotation( pack, annotation ); } + private void inspectRootElement(Element element, @Nullable Element parent, @Nullable TypeElement primaryEntity) { + if ( !included( element ) + || hasAnnotation( element, Constants.EXCLUDE ) + || hasPackageAnnotation( element, Constants.EXCLUDE ) + || element.getModifiers().contains( Modifier.PRIVATE ) ) { + // skip it completely + return; + } + else if ( isEntityOrEmbeddable( element ) + && !element.getModifiers().contains( Modifier.PRIVATE ) ) { + context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated entity class '" + element + "'" ); + handleRootElementAnnotationMirrors( element, parent ); + } + else if ( hasAuxiliaryAnnotations( element ) ) { + context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class '" + element + "'" ); + handleRootElementAuxiliaryAnnotationMirrors( element ); + } + else if ( element instanceof TypeElement typeElement ) { + final AnnotationMirror repository = getAnnotationMirror( element, JD_REPOSITORY ); + if ( repository != null ) { + final AnnotationValue provider = getAnnotationValue( repository, "provider" ); + if ( provider == null + || provider.getValue().toString().isEmpty() + || provider.getValue().toString().equalsIgnoreCase("hibernate") ) { + context.logMessage( Diagnostic.Kind.OTHER, "Processing repository class '" + element + "'" ); + final AnnotationMetaEntity metaEntity = + AnnotationMetaEntity.create( typeElement, context, + parentMetadata( parent, context::getMetaEntity ), + primaryEntity ); + if ( metaEntity.isInitialized() ) { + context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity ); + } + // otherwise discard it (assume it has query by magical method name stuff) + } + } + else { + for ( Element member : typeElement.getEnclosedElements() ) { + if ( hasAnnotation( member, HQL, SQL, FIND ) ) { + context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class '" + element + "'" ); + final AnnotationMetaEntity metaEntity = + AnnotationMetaEntity.create( typeElement, context, + parentMetadata( parent, context::getMetaEntity ), + primaryEntity ); + context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity ); + break; + } + } + if ( enclosesEntityOrEmbeddable( element ) ) { + final NonManagedMetamodel metaEntity = + NonManagedMetamodel.create( typeElement, context, false, + parentMetadata( parent, context::getMetamodel ) ); + context.addMetaEntity( metaEntity.getQualifiedName(), metaEntity ); + if ( context.generateJakartaDataStaticMetamodel() ) { + final NonManagedMetamodel dataMetaEntity = + NonManagedMetamodel.create( typeElement, context, true, + parentMetadata( parent, context::getDataMetaEntity ) ); + context.addDataMetaEntity( dataMetaEntity.getQualifiedName(), dataMetaEntity ); + } + + } + } + } + if ( isClassRecordOrInterfaceType( element ) ) { + for ( final Element child : element.getEnclosedElements() ) { + if ( isClassRecordOrInterfaceType( child ) ) { + processElement( child, element ); + } + } + } + } + private void createMetaModelClasses() { for ( Metamodel aux : context.getMetaAuxiliaries() ) { @@ -644,6 +650,7 @@ private void handleRootElementAnnotationMirrors(final Element element, @Nullable final TypeElement typeElement = (TypeElement) element; indexEntityName( typeElement ); indexEnumFields( typeElement ); + indexQueryInterfaces( typeElement ); final String qualifiedName = typeElement.getQualifiedName().toString(); final Metamodel alreadyExistingMetaEntity = @@ -700,6 +707,14 @@ private static boolean hasHandwrittenMetamodel(Element element) { .contentEquals('_' + element.getSimpleName().toString())); } + private void indexQueryInterfaces(TypeElement typeElement) { + for ( Element element : typeElement.getEnclosedElements() ) { + if( element.getKind() == ElementKind.INTERFACE ) { + inspectRootElement( element, typeElement, typeElement ); + } + } + } + private void indexEntityName(TypeElement typeElement) { final AnnotationMirror mirror = getAnnotationMirror( typeElement, ENTITY ); if ( mirror != null ) { From 0f84876414d3125d37e456052228e3793fd2ebcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Wed, 25 Jun 2025 15:28:59 +0200 Subject: [PATCH 4/9] [HHH-19586] Avoid inspecting private interface methods --- .../org/hibernate/processor/annotation/AnnotationMetaEntity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java index 797b6834364a..85c5f18cd107 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java @@ -414,6 +414,7 @@ else if ( hasAnnotation( method, JD_DELETE ) ) { } else if ( method.getEnclosingElement().getKind().isInterface() && !method.isDefault() + && !method.getModifiers().contains(Modifier.PRIVATE) && !isSessionGetter(method) ) { final String companionClassName = element.getQualifiedName().toString() + '$'; if ( context.getElementUtils().getTypeElement(companionClassName) == null ) { From 6c77e850d11515c7a5f4c2cee8543e632e264626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Wed, 25 Jun 2025 15:29:47 +0200 Subject: [PATCH 5/9] [HHH-19586] Generate repository accessors in the metamodel of Panache2 entities Comes with 4 out of the box: - managed/blocking (generated) - managed/reactive (generated if reactive-common is in the CP) - stateless/blocking (generated) - stateless/reactive (generated if reactive-common is in the CP) - whatever nested repositories from the entity -- and if they implement one of the first four, we use this instead of the generated one --- .../annotation/AnnotationMetaEntity.java | 89 +++++++++++++++ .../annotation/CDIAccessorMetaAttribute.java | 106 ++++++++++++++++++ .../annotation/CDITypeMetaAttribute.java | 92 +++++++++++++++ 3 files changed, 287 insertions(+) create mode 100644 tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CDIAccessorMetaAttribute.java create mode 100644 tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CDITypeMetaAttribute.java diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java index 85c5f18cd107..779bdbce3087 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java @@ -468,6 +468,10 @@ && containsAnnotation( method, HQL, SQL, FIND ) ) { addPersistentMembers( gettersAndSettersOfClass, AccessType.PROPERTY ); addIdClassIfNeeded( fieldsOfClass, gettersAndSettersOfClass ); + + if( hasAnnotation( element, ENTITY) && isPanache2Type(element) && !jakartaDataStaticModel ) { + addRepositoryMembers( element ); + } } addAuxiliaryMembers(); @@ -521,6 +525,41 @@ private void addIdClassIfNeeded(List fields, List getIdMemberNames(List fields, List methods) { final List components = new ArrayList<>(); for ( var field : fields ) { @@ -648,6 +687,56 @@ private boolean isEquivalentPrimitiveType(TypeMirror type, TypeMirror match) { && isSameType( context.getTypeUtils().boxedClass( ((PrimitiveType) type) ).asType(), match ); } + private void addAccessors(@Nullable Element repositoryType, @Nullable TypeMirror idType, + String repositoryAccessor, String repositorySuperType) { + TypeElement finalPrimaryEntity = primaryEntity; + if ( repositoryType != null ) { + members.put( repositoryAccessor, new CDIAccessorMetaAttribute( this, repositoryAccessor, repositoryType.getSimpleName().toString() ) ); + } + else if ( idType != null && finalPrimaryEntity != null ) { + String repositoryTypeName = "Panache"+repositoryAccessor.substring(0,1).toUpperCase()+repositoryAccessor.substring(1)+"Repository"; + members.put( repositoryAccessor, new CDIAccessorMetaAttribute( this, repositoryAccessor, repositoryTypeName ) ); + members.put( repositoryAccessor + "Repository", new CDITypeMetaAttribute( this, repositoryTypeName, repositorySuperType +"<"+ finalPrimaryEntity.getSimpleName()+", "+ idType.toString()+">" ) ); + } + } + + private @Nullable TypeMirror findIdType() { + Element idMember = findIdMember(); + TypeElement primaryEntityForTest = primaryEntity; + if ( idMember != null && primaryEntityForTest != null ) { + TypeMirror typedIdMember = this.context.getTypeUtils().asMemberOf((DeclaredType) primaryEntityForTest.asType(), idMember); + return switch(typedIdMember.getKind()) { + case ARRAY, DECLARED, BOOLEAN, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE -> typedIdMember; + case EXECUTABLE -> ((ExecutableType) typedIdMember).getReturnType(); + default -> { + message( element, + "Unhandled id member kind: "+typedIdMember+" for id "+idMember, + Diagnostic.Kind.ERROR ); + yield null; + } + }; + } + return null; + } + + private @Nullable Element findIdMember() { + if ( primaryEntity == null ) { + message( element, + "No primary entity defined to find id member", + Diagnostic.Kind.ERROR ); + return null; + } + for ( Element member : context.getAllMembers( primaryEntity ) ) { + if ( hasAnnotation( member, ID, EMBEDDED_ID ) ) { + return member; + } + } + message( element, + "Could not find any member annotated with @Id or @EmbeddedId", + Diagnostic.Kind.ERROR ); + return null; + } + private boolean checkEntities(List lifecycleMethods, boolean hibernateRepo) { boolean foundPersistenceEntity = false; VariableElement nonPersistenceParameter = null; diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CDIAccessorMetaAttribute.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CDIAccessorMetaAttribute.java new file mode 100644 index 000000000000..22babb13d35d --- /dev/null +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CDIAccessorMetaAttribute.java @@ -0,0 +1,106 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.annotation; + +import javax.lang.model.element.Element; + +import org.hibernate.processor.model.MetaAttribute; +import org.hibernate.processor.model.Metamodel; +import org.hibernate.processor.util.StringUtil; + +public class CDIAccessorMetaAttribute implements MetaAttribute { + + private AnnotationMetaEntity annotationMetaEntity; + private String propertyName; + private String typeName; + + public CDIAccessorMetaAttribute(AnnotationMetaEntity annotationMetaEntity, Element repositoryElement) { + this.annotationMetaEntity = annotationMetaEntity; + // turn the name into lowercase + String name = repositoryElement.getSimpleName().toString(); + // FIXME: this is wrong for types like STEFQueries + this.propertyName = StringUtil.decapitalize( name ); + this.typeName = name; + } + + public CDIAccessorMetaAttribute(AnnotationMetaEntity annotationMetaEntity, String propertyName, String className) { + this.annotationMetaEntity = annotationMetaEntity; + this.propertyName = propertyName; + this.typeName = className; + } + + @Override + public boolean hasTypedAttribute() { + return true; + } + + @Override + public boolean hasStringAttribute() { + return false; + } + + @Override + public String getAttributeDeclarationString() { + final StringBuilder declaration = new StringBuilder(); + modifiers( declaration ); + preamble( declaration ); + returnCDI( declaration ); + closingBrace( declaration ); + return declaration.toString(); + } + + private void returnCDI(StringBuilder declaration) { + annotationMetaEntity.importType("jakarta.enterprise.inject.spi.CDI"); + declaration + .append("\treturn CDI.current().select(") + .append(typeName) + .append(".class).get();\n"); + } + + void closingBrace(StringBuilder declaration) { + declaration.append("}"); + } + + void preamble(StringBuilder declaration) { + declaration + .append(typeName) + .append(" ") + .append( getPropertyName() ); + declaration + .append("() {\n"); + } + + @Override + public String getAttributeNameDeclarationString() { + return ""; + } + + @Override + public String getMetaType() { + throw new UnsupportedOperationException("operation not supported"); + } + + @Override + public String getPropertyName() { + return propertyName; + } + + @Override + public String getTypeDeclaration() { + return ""; + } + + void modifiers(StringBuilder declaration) { + declaration + .append("\npublic static "); + } + + + @Override + public Metamodel getHostingEntity() { + return annotationMetaEntity; + } + +} diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CDITypeMetaAttribute.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CDITypeMetaAttribute.java new file mode 100644 index 000000000000..868fdb6c9102 --- /dev/null +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CDITypeMetaAttribute.java @@ -0,0 +1,92 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.processor.annotation; + +import org.hibernate.processor.HibernateProcessor; +import org.hibernate.processor.model.MetaAttribute; +import org.hibernate.processor.model.Metamodel; + +public class CDITypeMetaAttribute implements MetaAttribute { + + private AnnotationMetaEntity annotationMetaEntity; + private String typeName; + private Object superTypeName; + + public CDITypeMetaAttribute(AnnotationMetaEntity annotationMetaEntity, String className, String superTypeName) { + this.annotationMetaEntity = annotationMetaEntity; + this.superTypeName = superTypeName; + this.typeName = className; + } + + @Override + public boolean hasTypedAttribute() { + return true; + } + + @Override + public boolean hasStringAttribute() { + return false; + } + + @Override + public String getAttributeDeclarationString() { + final StringBuilder declaration = new StringBuilder(); + modifiers( declaration ); + preamble( declaration ); + closingBrace( declaration ); + return declaration.toString(); + } + + void closingBrace(StringBuilder declaration) { + declaration.append("}"); + } + + void preamble(StringBuilder declaration) { + declaration + .append("class ") + .append(typeName) + .append(" implements ") + .append( superTypeName ); + declaration + .append(" {\n"); + } + + @Override + public String getAttributeNameDeclarationString() { + return ""; + } + + @Override + public String getMetaType() { + throw new UnsupportedOperationException("operation not supported"); + } + + @Override + public String getPropertyName() { + return ""; + } + + @Override + public String getTypeDeclaration() { + return ""; + } + + void modifiers(StringBuilder declaration) { + annotationMetaEntity.importType("jakarta.annotation.Generated"); + annotationMetaEntity.importType("jakarta.enterprise.context.Dependent"); + declaration + .append("\n@Dependent\n") + .append("@Generated(\""+HibernateProcessor.class.getName()+"\")\n"); + declaration + .append("public static "); + } + + + @Override + public Metamodel getHostingEntity() { + return annotationMetaEntity; + } + +} From 1799b431a9a590f0cfaa7dc2fc617ee9f50caaef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Tue, 25 Feb 2025 15:23:52 +0100 Subject: [PATCH 6/9] [HHH-19586] Make sure inheritance test works with interfaces --- .../main/java/org/hibernate/processor/util/TypeUtils.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java index a8ef4f810d47..0530ac5cb945 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java @@ -657,6 +657,14 @@ public static boolean implementsInterface(TypeElement type, String interfaceName } } } + TypeMirror superclass = type.getSuperclass(); + if ( superclass != null && superclass.getKind() == TypeKind.DECLARED ) { + final DeclaredType declaredType = (DeclaredType) superclass; + final TypeElement typeElement = (TypeElement) declaredType.asElement(); + if( implementsInterface( typeElement, interfaceName) ) { + return true; + } + } return false; } From ca6659545932f48d9eea4da74a049ad15ebcc708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Tue, 27 May 2025 16:28:50 +0200 Subject: [PATCH 7/9] [HHH-19586] For Panache 2 repositories, look for any annotated method returning a Uni to decide for reactive session --- .../annotation/AnnotationMetaEntity.java | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java index 779bdbce3087..b960f78161c9 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java @@ -1037,12 +1037,32 @@ private String addDaoConstructor(@Nullable ExecutableElement method) { * is for repositories */ private String setupQuarkusDaoConstructor(@Nullable ExecutableElement getter, @Nullable TypeElement element) { - if ( context.usesQuarkusOrm() - || (context.usesQuarkusPanache2() - && element != null - && (implementsInterface(element, PANACHE2_MANAGED_BLOCKING_REPOSITORY_BASE) - || implementsInterface(element, PANACHE2_STATELESS_BLOCKING_REPOSITORY_BASE))) + boolean favorBlocking = context.usesQuarkusOrm() + || (context.usesQuarkusPanache2() + && element != null + && (implementsInterface(element, PANACHE2_MANAGED_BLOCKING_REPOSITORY_BASE) + || implementsInterface(element, PANACHE2_STATELESS_BLOCKING_REPOSITORY_BASE))); + if ( context.usesQuarkusPanache2() + && element != null + && !implementsInterface(element, PANACHE2_MANAGED_BLOCKING_REPOSITORY_BASE) + && !implementsInterface(element, PANACHE2_STATELESS_BLOCKING_REPOSITORY_BASE) + && !implementsInterface(element, PANACHE2_MANAGED_REACTIVE_REPOSITORY_BASE) + && !implementsInterface(element, PANACHE2_STATELESS_REACTIVE_REPOSITORY_BASE) + // FIXME: add other default for JD repos? ) { + // look for any annotated method, see if they return a Uni + final List methodsOfClass = + methodsIn( context.getAllMembers( element ) ); + for ( ExecutableElement method : methodsOfClass ) { + // trust the first method, no need to look for them all + if ( containsAnnotation( method, HQL, SQL, JD_QUERY, FIND, JD_FIND ) ) { + favorBlocking = !isUni( method.getReturnType() ); + break; + } + } + } + // FIXME: probably go in this branch if we have a getter too? + if ( favorBlocking ) { String name; String sessionType; if ( getter != null ) { @@ -1598,6 +1618,15 @@ private static TypeMirror ununi(TypeMirror returnType) { return returnType; } + private static boolean isUni (TypeMirror returnType){ + if ( returnType.getKind() == TypeKind.DECLARED ) { + final DeclaredType declaredType = (DeclaredType) returnType; + final TypeElement typeElement = (TypeElement) declaredType.asElement(); + return typeElement.getQualifiedName().contentEquals( Constants.UNI ); + } + return false; + } + private static boolean isLegalRawResultType(String containerTypeName) { return LEGAL_RAW_RESULT_TYPES.contains( containerTypeName ); } From ddc18bbf37ec2fc933c84b72a17142080f5c09af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Fri, 27 Jun 2025 14:27:35 +0200 Subject: [PATCH 8/9] [HHH-19586] Update tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java Co-authored-by: Gavin King --- .../src/main/java/org/hibernate/processor/util/TypeUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java index 0530ac5cb945..7bd6acf9f0c1 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java @@ -661,7 +661,7 @@ public static boolean implementsInterface(TypeElement type, String interfaceName if ( superclass != null && superclass.getKind() == TypeKind.DECLARED ) { final DeclaredType declaredType = (DeclaredType) superclass; final TypeElement typeElement = (TypeElement) declaredType.asElement(); - if( implementsInterface( typeElement, interfaceName) ) { + if ( implementsInterface( typeElement, interfaceName) ) { return true; } } From e571675b97f3f0b1d414e2142b9f990420b341a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Tue, 1 Jul 2025 14:43:25 +0200 Subject: [PATCH 9/9] [HHH-19586] Support `@IdClass` in Panache 2 repositories --- .../annotation/AnnotationMetaEntity.java | 16 ++++++++++++-- .../hibernate/processor/util/TypeUtils.java | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java index b960f78161c9..2596ffca76a5 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java @@ -95,6 +95,7 @@ import static org.hibernate.processor.util.TypeUtils.getAnnotationMirror; import static org.hibernate.processor.util.TypeUtils.getAnnotationValue; import static org.hibernate.processor.util.TypeUtils.getGeneratedClassFullyQualifiedName; +import static org.hibernate.processor.util.TypeUtils.getInheritedAnnotationMirror; import static org.hibernate.processor.util.TypeUtils.hasAnnotation; import static org.hibernate.processor.util.TypeUtils.implementsInterface; import static org.hibernate.processor.util.TypeUtils.primitiveClassMatchesKind; @@ -701,9 +702,20 @@ else if ( idType != null && finalPrimaryEntity != null ) { } private @Nullable TypeMirror findIdType() { - Element idMember = findIdMember(); TypeElement primaryEntityForTest = primaryEntity; - if ( idMember != null && primaryEntityForTest != null ) { + if ( primaryEntityForTest == null ) { + return null; + } + AnnotationMirror idClass = getInheritedAnnotationMirror( this.context.getElementUtils(), primaryEntityForTest, ID_CLASS ); + if ( idClass != null ) { + AnnotationValue value = getAnnotationValue(idClass, "value" ); + // I don't think this can have a null value + if ( value != null ) { + return (TypeMirror) value.getValue(); + } + } + Element idMember = findIdMember(); + if ( idMember != null ) { TypeMirror typedIdMember = this.context.getTypeUtils().asMemberOf((DeclaredType) primaryEntityForTest.asType(), idMember); return switch(typedIdMember.getKind()) { case ARRAY, DECLARED, BOOLEAN, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE -> typedIdMember; diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java index 7bd6acf9f0c1..8be4f51d2f86 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java @@ -27,6 +27,7 @@ import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor8; import javax.tools.Diagnostic; import java.util.HashMap; @@ -247,6 +248,26 @@ public static boolean isAnnotationMirrorOfType(AnnotationMirror annotationMirror return null; } + /** + * Checks whether the {@code Element} hosts the annotation (directly or inherited) with the given fully qualified class name. + * + * @param element the element to check for the hosted annotation + * @param qualifiedName the fully qualified class name of the annotation to check for + * + * @return the annotation mirror for the specified annotation class from the {@code Element} or {@code null} in case + * the {@code TypeElement} does not host the specified annotation (directly or inherited). + */ + public static @Nullable AnnotationMirror getInheritedAnnotationMirror(Elements elements, Element element, String qualifiedName) { + assert element != null; + assert qualifiedName != null; + for ( AnnotationMirror mirror : elements.getAllAnnotationMirrors(element) ) { + if ( isAnnotationMirrorOfType( mirror, qualifiedName ) ) { + return mirror; + } + } + return null; + } + public static boolean hasAnnotation(Element element, String qualifiedName) { return getAnnotationMirror( element, qualifiedName ) != null; }