diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/Book.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/Book.java new file mode 100644 index 0000000000..f34cf32c89 --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/Book.java @@ -0,0 +1,23 @@ +package org.hibernate.processor.test.data.superdao; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import org.hibernate.annotations.NaturalId; +import org.hibernate.processor.test.hqlsql.Publisher; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@Entity +public class Book { + @Id String isbn; + @NaturalId String title; + String text; + @NaturalId String authorName; + @ManyToOne + Publisher publisher; + BigDecimal price; + int pages; + LocalDate publicationDate; +} diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/Repo.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/Repo.java new file mode 100644 index 0000000000..56dfca2fa8 --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/Repo.java @@ -0,0 +1,10 @@ +package org.hibernate.processor.test.data.superdao; + +import jakarta.data.repository.Repository; +import org.hibernate.annotations.processing.Find; + +@Repository +public interface Repo extends SuperRepo { + @Find + Book get(String isbn); +} diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/SuperRepo.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/SuperRepo.java new file mode 100644 index 0000000000..b0b46467e4 --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/SuperRepo.java @@ -0,0 +1,17 @@ +package org.hibernate.processor.test.data.superdao; + +import jakarta.data.repository.Repository; +import org.hibernate.annotations.processing.Find; +import org.hibernate.annotations.processing.HQL; +import org.hibernate.annotations.processing.Pattern; + +import java.util.List; + +@Repository +public interface SuperRepo { + @Find + List books1(@Pattern String title); + + @HQL("where title like :title") + List books2(String title); +} diff --git a/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/SuperRepoTest.java b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/SuperRepoTest.java new file mode 100644 index 0000000000..26e7304ea0 --- /dev/null +++ b/tooling/metamodel-generator/src/jakartaData/java/org/hibernate/processor/test/data/superdao/SuperRepoTest.java @@ -0,0 +1,29 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.processor.test.data.superdao; + +import org.hibernate.processor.test.util.CompilationTest; +import org.hibernate.processor.test.util.TestUtil; +import org.hibernate.processor.test.util.WithClasses; +import org.junit.Test; + +import static org.hibernate.processor.test.util.TestUtil.assertMetamodelClassGeneratedFor; + +/** + * @author Gavin King + */ +public class SuperRepoTest extends CompilationTest { + @Test + @WithClasses({ Book.class, SuperRepo.class, Repo.class }) + public void testQueryMethod() { + System.out.println( TestUtil.getMetaModelSourceAsString( SuperRepo.class ) ); + System.out.println( TestUtil.getMetaModelSourceAsString( Repo.class ) ); + assertMetamodelClassGeneratedFor( Book.class ); + assertMetamodelClassGeneratedFor( SuperRepo.class ); + assertMetamodelClassGeneratedFor( Repo.class ); + } +} 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 c7ca33d793..640321ea39 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 @@ -137,6 +137,11 @@ public class AnnotationMetaEntity extends AnnotationMeta { */ private boolean repository = false; + /** + * A repository type that this repository inherits. + */ + private @Nullable TypeMirror superRepository; + /** * The type of the "session getter" method of a DAO-style repository. */ @@ -215,7 +220,20 @@ public final String getQualifiedName() { @Override public @Nullable String getSupertypeName() { - return findMappedSuperClass( this, context ); + if ( repository ) { + if ( superRepository == null ) { + return null; + } + else { + final DeclaredType declaredType = (DeclaredType) superRepository; + final TypeElement typeElement = (TypeElement) declaredType.asElement(); + // the import should already have been added earlier + return importType( typeElement.getQualifiedName().toString() ); + } + } + else { + return findMappedSuperClass( this, context ); + } } @Override @@ -354,6 +372,13 @@ else if ( containsAnnotation( method, JD_INSERT, JD_UPDATE, JD_DELETE, JD_SAVE ) setupSession(); + if ( repository ) { + superRepository = findSuperRepository( element ); + if ( superRepository != null ) { + importType( superRepository.toString() ); + } + } + if ( managed && !jakartaDataStaticModel ) { putMember( "class", new AnnotationMetaType(this) ); } @@ -427,6 +452,30 @@ else if ( element.getKind() == ElementKind.INTERFACE } } + private @Nullable TypeMirror findSuperRepository(TypeElement type) { + for ( TypeMirror superinterface : type.getInterfaces() ) { + if ( superinterface.getKind() == TypeKind.DECLARED ) { + final DeclaredType declaredType = (DeclaredType) superinterface; + final TypeElement typeElement = (TypeElement) declaredType.asElement(); + if ( hasAnnotation( typeElement, JD_REPOSITORY ) ) { + return superinterface; + } + else if ( typeElement.getEnclosedElements().stream() + .anyMatch( member -> hasAnnotation( member, + HQL, SQL, JD_QUERY, FIND, JD_FIND, JD_INSERT, JD_UPDATE, JD_DELETE, JD_SAVE ) ) ) { + return superinterface; + } + else { + final TypeMirror ret = findSuperRepository( typeElement ); + if ( ret != null ) { + return ret; + } + } + } + } + return null; + } + private @Nullable ExecutableElement findSessionGetter(TypeElement type) { if ( !hasAnnotation( type, ENTITY, MAPPED_SUPERCLASS, EMBEDDABLE ) || isPanacheType( type ) ) { @@ -438,7 +487,7 @@ else if ( element.getKind() == ElementKind.INTERFACE final TypeMirror superclass = type.getSuperclass(); if ( superclass.getKind() == TypeKind.DECLARED ) { final DeclaredType declaredType = (DeclaredType) superclass; - ExecutableElement ret = findSessionGetter( (TypeElement) declaredType.asElement() ); + final ExecutableElement ret = findSessionGetter( (TypeElement) declaredType.asElement() ); if ( ret != null ) { return ret; } @@ -446,7 +495,7 @@ else if ( element.getKind() == ElementKind.INTERFACE for ( TypeMirror superinterface : type.getInterfaces() ) { if ( superinterface.getKind() == TypeKind.DECLARED ) { final DeclaredType declaredType = (DeclaredType) superinterface; - ExecutableElement ret = findSessionGetter( (TypeElement) declaredType.asElement() ); + final ExecutableElement ret = findSessionGetter( (TypeElement) declaredType.asElement() ); if ( ret != null ) { return ret; } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/DefaultConstructor.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/DefaultConstructor.java index 393e5de733..32e8936900 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/DefaultConstructor.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/DefaultConstructor.java @@ -57,46 +57,49 @@ public boolean hasStringAttribute() { @Override public String getAttributeDeclarationString() { - StringBuilder declaration = new StringBuilder(); - declaration.append('\n'); + final StringBuilder declaration = new StringBuilder(); declaration - .append("@") - .append(annotationMetaEntity.importType("jakarta.persistence.PersistenceUnit")); - if ( dataStore != null ) { + .append('\n'); + if ( annotationMetaEntity.getSupertypeName() == null ) { declaration - .append("(unitName=\"") - .append(dataStore) - .append("\")"); + .append("@") + .append(annotationMetaEntity.importType("jakarta.persistence.PersistenceUnit")); + if ( dataStore != null ) { + declaration + .append("(unitName=\"") + .append(dataStore) + .append("\")"); + } + declaration + .append("\nprivate ") + .append(annotationMetaEntity.importType(ENTITY_MANAGER_FACTORY)) + .append(" ") + .append(sessionVariableName) + .append("Factory;\n\n"); + declaration.append('@') + .append(annotationMetaEntity.importType("jakarta.annotation.PostConstruct")) + .append("\nprivate void openSession() {") + .append("\n\t") + .append(sessionVariableName) + .append(" = ") + .append(sessionVariableName) + .append("Factory.unwrap(") + .append(annotationMetaEntity.importType(HIB_SESSION_FACTORY)) + .append(".class).openStatelessSession();") + .append("\n}\n\n"); + declaration.append('@') + .append(annotationMetaEntity.importType("jakarta.annotation.PreDestroy")) + .append("\nprivate void closeSession() {") + .append("\n\t") + .append(sessionVariableName) + .append(".close();") + .append("\n}\n\n"); } - declaration - .append("\nprivate ") - .append(annotationMetaEntity.importType(ENTITY_MANAGER_FACTORY)) - .append(" ") - .append(sessionVariableName) - .append("Factory;\n\n"); inject( declaration ); declaration .append(constructorName) .append("(") .append(") {") - .append("\n}\n\n"); - declaration.append('@') - .append(annotationMetaEntity.importType("jakarta.annotation.PostConstruct")) - .append("\nprivate void openSession() {") - .append("\n\t") - .append(sessionVariableName) - .append(" = ") - .append(sessionVariableName) - .append("Factory.unwrap(") - .append(annotationMetaEntity.importType(HIB_SESSION_FACTORY)) - .append(".class).openStatelessSession();") - .append("\n}\n\n"); - declaration.append('@') - .append(annotationMetaEntity.importType("jakarta.annotation.PreDestroy")) - .append("\nprivate void closeSession() {") - .append("\n\t") - .append(sessionVariableName) - .append(".close();") .append("\n}"); return declaration.toString(); } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/RepositoryConstructor.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/RepositoryConstructor.java index c78e292ed5..5523047706 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/RepositoryConstructor.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/RepositoryConstructor.java @@ -66,23 +66,26 @@ public boolean hasStringAttribute() { @Override public String getAttributeDeclarationString() { - StringBuilder declaration = new StringBuilder(); + final StringBuilder declaration = new StringBuilder(); declaration - .append("\nprivate "); - if ( !dataRepository ) { - // don't mark the field final - // because it will be initialized - // in @PostConstruct + .append('\n'); + if ( annotationMetaEntity.getSupertypeName() == null ) { declaration - .append("final "); + .append("protected "); + if ( !dataRepository ) { + // don't mark the field final + // because it will be initialized + // in @PostConstruct + declaration + .append("final "); + } + notNull( declaration ); + declaration + .append(annotationMetaEntity.importType(sessionTypeName)) + .append(" ") + .append(sessionVariableName) + .append(";\n\n"); } - notNull( declaration ); - declaration - .append(annotationMetaEntity.importType(sessionTypeName)) - .append(" ") - .append(sessionVariableName) - .append(";") - .append("\n\n"); inject( declaration ); declaration .append("public ") @@ -94,29 +97,41 @@ public String getAttributeDeclarationString() { .append(annotationMetaEntity.importType(sessionTypeName)) .append(" ") .append(sessionVariableName) - .append(") {") - .append("\n\tthis.") - .append(sessionVariableName) - .append(" = ") - .append(sessionVariableName) - .append(";") - .append("\n}") - .append("\n\n"); - if (addOverrideAnnotation) { - declaration.append("@Override\n"); + .append(") {\n"); + if ( annotationMetaEntity.getSupertypeName() != null ) { + declaration + .append("\tsuper(") + .append(sessionVariableName) + .append(");\n"); + } + else { + declaration + .append("\tthis.") + .append(sessionVariableName) + .append(" = ") + .append(sessionVariableName) + .append(";\n"); } declaration - .append("public "); - notNull( declaration ); - declaration - .append(annotationMetaEntity.importType(sessionTypeName)) - .append(" ") - .append(methodName) - .append("() {") - .append("\n\treturn ") - .append(sessionVariableName) - .append(";") - .append("\n}"); + .append("}"); + if ( annotationMetaEntity.getSupertypeName() == null ) { + declaration + .append("\n\n"); + if (addOverrideAnnotation) { + declaration.append("@Override\n"); + } + declaration + .append("public "); + notNull( declaration ); + declaration + .append(annotationMetaEntity.importType(sessionTypeName)) + .append(" ") + .append(methodName) + .append("() {") + .append("\n\treturn ") + .append(sessionVariableName) + .append(";\n}"); + } return declaration.toString(); } 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 bf16180379..3c5090375f 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 @@ -612,17 +612,18 @@ else if ( name.startsWith( "is" ) ) { } public static @Nullable String findMappedSuperClass(Metamodel entity, Context context) { - Element element = entity.getElement(); + final Element element = entity.getElement(); if ( element instanceof TypeElement ) { - TypeMirror superClass = ((TypeElement) element).getSuperclass(); + final TypeElement typeElement = (TypeElement) element; + TypeMirror superClass = typeElement.getSuperclass(); //superclass of Object is of NoType which returns some other kind while ( superClass.getKind() == TypeKind.DECLARED ) { - final Element superClassElement = ( (DeclaredType) superClass ).asElement(); - String superClassName = ( (TypeElement) superClassElement ).getQualifiedName().toString(); + final DeclaredType declaredType = (DeclaredType) superClass; + final TypeElement superClassElement = (TypeElement) declaredType.asElement(); if ( extendsSuperMetaModel( superClassElement, entity.isMetaComplete(), context ) ) { - return superClassName; + return superClassElement.getQualifiedName().toString(); } - superClass = ( (TypeElement) superClassElement ).getSuperclass(); + superClass = superClassElement.getSuperclass(); } } return null; @@ -643,21 +644,14 @@ else if ( name.startsWith( "is" ) ) { */ private static boolean extendsSuperMetaModel(Element superClassElement, boolean entityMetaComplete, Context context) { // if we processed the superclass in the same run we definitely need to extend - String superClassName = ( (TypeElement) superClassElement ).getQualifiedName().toString(); - if ( context.containsMetaEntity( superClassName ) - || context.containsMetaEmbeddable( superClassName ) ) { - return true; - } - - // to allow for the case that the metamodel class for the super entity is for example contained in another - // jar file we use reflection. However, we need to consider the fact that there is xml configuration - // and annotations should be ignored - if ( !entityMetaComplete - && containsAnnotation( superClassElement, Constants.ENTITY, Constants.MAPPED_SUPERCLASS ) ) { - return true; - } - - return false; + final TypeElement typeElement = (TypeElement) superClassElement; + final String superClassName = typeElement.getQualifiedName().toString(); + return context.containsMetaEntity( superClassName ) + || context.containsMetaEmbeddable( superClassName ) + // to allow for the case that the metamodel class for the super entity is for example contained in another + // jar file we use reflection. However, we need to consider the fact that there is xml configuration + // and annotations should be ignored + || !entityMetaComplete && containsAnnotation( superClassElement, ENTITY, MAPPED_SUPERCLASS ); } static class EmbeddedAttributeVisitor extends SimpleTypeVisitor8<@Nullable String, Element> { diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/Book.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/Book.java new file mode 100644 index 0000000000..57ae8f63cb --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/Book.java @@ -0,0 +1,23 @@ +package org.hibernate.processor.test.superdao; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import org.hibernate.annotations.NaturalId; +import org.hibernate.processor.test.hqlsql.Publisher; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@Entity +public class Book { + @Id String isbn; + @NaturalId String title; + String text; + @NaturalId String authorName; + @ManyToOne + Publisher publisher; + BigDecimal price; + int pages; + LocalDate publicationDate; +} diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/Dao.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/Dao.java new file mode 100644 index 0000000000..0eebd94288 --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/Dao.java @@ -0,0 +1,8 @@ +package org.hibernate.processor.test.superdao; + +import org.hibernate.annotations.processing.Find; + +public interface Dao extends SuperDao { + @Find + Book get(String isbn); +} diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/SuperDao.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/SuperDao.java new file mode 100644 index 0000000000..a8966f742a --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/SuperDao.java @@ -0,0 +1,20 @@ +package org.hibernate.processor.test.superdao; + +import jakarta.persistence.EntityManager; +import org.hibernate.annotations.processing.Find; +import org.hibernate.annotations.processing.HQL; +import org.hibernate.annotations.processing.Pattern; + +import java.util.List; + +public interface SuperDao { + + EntityManager em(); + + + @Find + List books1(@Pattern String title); + + @HQL("where title like :title") + List books2(String title); +} diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/SuperDaoTest.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/SuperDaoTest.java new file mode 100644 index 0000000000..d4b215362f --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/superdao/SuperDaoTest.java @@ -0,0 +1,29 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.processor.test.superdao; + +import org.hibernate.processor.test.util.CompilationTest; +import org.hibernate.processor.test.util.TestUtil; +import org.hibernate.processor.test.util.WithClasses; +import org.junit.Test; + +import static org.hibernate.processor.test.util.TestUtil.assertMetamodelClassGeneratedFor; + +/** + * @author Gavin King + */ +public class SuperDaoTest extends CompilationTest { + @Test + @WithClasses({ Book.class, SuperDao.class, Dao.class }) + public void testQueryMethod() { + System.out.println( TestUtil.getMetaModelSourceAsString( SuperDao.class ) ); + System.out.println( TestUtil.getMetaModelSourceAsString( Dao.class ) ); + assertMetamodelClassGeneratedFor( Book.class ); + assertMetamodelClassGeneratedFor( SuperDao.class ); + assertMetamodelClassGeneratedFor( Dao.class ); + } +}