HHH-17873 initial impl of repository inheritance

needed for Jakarta Data, and useful
limited to single inheritance for now
This commit is contained in:
Gavin King 2024-03-20 22:49:55 +01:00
parent b34462138d
commit 9305610a32
12 changed files with 311 additions and 91 deletions

View File

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

View File

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

View File

@ -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<Book> books1(@Pattern String title);
@HQL("where title like :title")
List<Book> books2(String title);
}

View File

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

View File

@ -137,6 +137,11 @@ public class AnnotationMetaEntity extends AnnotationMeta {
*/ */
private boolean repository = false; 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. * The type of the "session getter" method of a DAO-style repository.
*/ */
@ -215,7 +220,20 @@ public class AnnotationMetaEntity extends AnnotationMeta {
@Override @Override
public @Nullable String getSupertypeName() { 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 @Override
@ -354,6 +372,13 @@ public class AnnotationMetaEntity extends AnnotationMeta {
setupSession(); setupSession();
if ( repository ) {
superRepository = findSuperRepository( element );
if ( superRepository != null ) {
importType( superRepository.toString() );
}
}
if ( managed && !jakartaDataStaticModel ) { if ( managed && !jakartaDataStaticModel ) {
putMember( "class", new AnnotationMetaType(this) ); putMember( "class", new AnnotationMetaType(this) );
} }
@ -427,6 +452,30 @@ public class AnnotationMetaEntity extends AnnotationMeta {
} }
} }
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) { private @Nullable ExecutableElement findSessionGetter(TypeElement type) {
if ( !hasAnnotation( type, ENTITY, MAPPED_SUPERCLASS, EMBEDDABLE ) if ( !hasAnnotation( type, ENTITY, MAPPED_SUPERCLASS, EMBEDDABLE )
|| isPanacheType( type ) ) { || isPanacheType( type ) ) {
@ -438,7 +487,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
final TypeMirror superclass = type.getSuperclass(); final TypeMirror superclass = type.getSuperclass();
if ( superclass.getKind() == TypeKind.DECLARED ) { if ( superclass.getKind() == TypeKind.DECLARED ) {
final DeclaredType declaredType = (DeclaredType) superclass; final DeclaredType declaredType = (DeclaredType) superclass;
ExecutableElement ret = findSessionGetter( (TypeElement) declaredType.asElement() ); final ExecutableElement ret = findSessionGetter( (TypeElement) declaredType.asElement() );
if ( ret != null ) { if ( ret != null ) {
return ret; return ret;
} }
@ -446,7 +495,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
for ( TypeMirror superinterface : type.getInterfaces() ) { for ( TypeMirror superinterface : type.getInterfaces() ) {
if ( superinterface.getKind() == TypeKind.DECLARED ) { if ( superinterface.getKind() == TypeKind.DECLARED ) {
final DeclaredType declaredType = (DeclaredType) superinterface; final DeclaredType declaredType = (DeclaredType) superinterface;
ExecutableElement ret = findSessionGetter( (TypeElement) declaredType.asElement() ); final ExecutableElement ret = findSessionGetter( (TypeElement) declaredType.asElement() );
if ( ret != null ) { if ( ret != null ) {
return ret; return ret;
} }

View File

@ -57,46 +57,49 @@ public class DefaultConstructor implements MetaAttribute {
@Override @Override
public String getAttributeDeclarationString() { public String getAttributeDeclarationString() {
StringBuilder declaration = new StringBuilder(); final StringBuilder declaration = new StringBuilder();
declaration.append('\n');
declaration declaration
.append("@") .append('\n');
.append(annotationMetaEntity.importType("jakarta.persistence.PersistenceUnit")); if ( annotationMetaEntity.getSupertypeName() == null ) {
if ( dataStore != null ) {
declaration declaration
.append("(unitName=\"") .append("@")
.append(dataStore) .append(annotationMetaEntity.importType("jakarta.persistence.PersistenceUnit"));
.append("\")"); 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 ); inject( declaration );
declaration declaration
.append(constructorName) .append(constructorName)
.append("(") .append("(")
.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}"); .append("\n}");
return declaration.toString(); return declaration.toString();
} }

View File

@ -66,23 +66,26 @@ public class RepositoryConstructor implements MetaAttribute {
@Override @Override
public String getAttributeDeclarationString() { public String getAttributeDeclarationString() {
StringBuilder declaration = new StringBuilder(); final StringBuilder declaration = new StringBuilder();
declaration declaration
.append("\nprivate "); .append('\n');
if ( !dataRepository ) { if ( annotationMetaEntity.getSupertypeName() == null ) {
// don't mark the field final
// because it will be initialized
// in @PostConstruct
declaration 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 ); inject( declaration );
declaration declaration
.append("public ") .append("public ")
@ -94,29 +97,41 @@ public class RepositoryConstructor implements MetaAttribute {
.append(annotationMetaEntity.importType(sessionTypeName)) .append(annotationMetaEntity.importType(sessionTypeName))
.append(" ") .append(" ")
.append(sessionVariableName) .append(sessionVariableName)
.append(") {") .append(") {\n");
.append("\n\tthis.") if ( annotationMetaEntity.getSupertypeName() != null ) {
.append(sessionVariableName) declaration
.append(" = ") .append("\tsuper(")
.append(sessionVariableName) .append(sessionVariableName)
.append(";") .append(");\n");
.append("\n}") }
.append("\n\n"); else {
if (addOverrideAnnotation) { declaration
declaration.append("@Override\n"); .append("\tthis.")
.append(sessionVariableName)
.append(" = ")
.append(sessionVariableName)
.append(";\n");
} }
declaration declaration
.append("public "); .append("}");
notNull( declaration ); if ( annotationMetaEntity.getSupertypeName() == null ) {
declaration declaration
.append(annotationMetaEntity.importType(sessionTypeName)) .append("\n\n");
.append(" ") if (addOverrideAnnotation) {
.append(methodName) declaration.append("@Override\n");
.append("() {") }
.append("\n\treturn ") declaration
.append(sessionVariableName) .append("public ");
.append(";") notNull( declaration );
.append("\n}"); declaration
.append(annotationMetaEntity.importType(sessionTypeName))
.append(" ")
.append(methodName)
.append("() {")
.append("\n\treturn ")
.append(sessionVariableName)
.append(";\n}");
}
return declaration.toString(); return declaration.toString();
} }

View File

@ -612,17 +612,18 @@ public final class TypeUtils {
} }
public static @Nullable String findMappedSuperClass(Metamodel entity, Context context) { public static @Nullable String findMappedSuperClass(Metamodel entity, Context context) {
Element element = entity.getElement(); final Element element = entity.getElement();
if ( element instanceof TypeElement ) { 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 //superclass of Object is of NoType which returns some other kind
while ( superClass.getKind() == TypeKind.DECLARED ) { while ( superClass.getKind() == TypeKind.DECLARED ) {
final Element superClassElement = ( (DeclaredType) superClass ).asElement(); final DeclaredType declaredType = (DeclaredType) superClass;
String superClassName = ( (TypeElement) superClassElement ).getQualifiedName().toString(); final TypeElement superClassElement = (TypeElement) declaredType.asElement();
if ( extendsSuperMetaModel( superClassElement, entity.isMetaComplete(), context ) ) { if ( extendsSuperMetaModel( superClassElement, entity.isMetaComplete(), context ) ) {
return superClassName; return superClassElement.getQualifiedName().toString();
} }
superClass = ( (TypeElement) superClassElement ).getSuperclass(); superClass = superClassElement.getSuperclass();
} }
} }
return null; return null;
@ -643,21 +644,14 @@ public final class TypeUtils {
*/ */
private static boolean extendsSuperMetaModel(Element superClassElement, boolean entityMetaComplete, Context context) { private static boolean extendsSuperMetaModel(Element superClassElement, boolean entityMetaComplete, Context context) {
// if we processed the superclass in the same run we definitely need to extend // if we processed the superclass in the same run we definitely need to extend
String superClassName = ( (TypeElement) superClassElement ).getQualifiedName().toString(); final TypeElement typeElement = (TypeElement) superClassElement;
if ( context.containsMetaEntity( superClassName ) final String superClassName = typeElement.getQualifiedName().toString();
|| context.containsMetaEmbeddable( superClassName ) ) { return context.containsMetaEntity( superClassName )
return true; || 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
// to allow for the case that the metamodel class for the super entity is for example contained in another // and annotations should be ignored
// jar file we use reflection. However, we need to consider the fact that there is xml configuration || !entityMetaComplete && containsAnnotation( superClassElement, ENTITY, MAPPED_SUPERCLASS );
// and annotations should be ignored
if ( !entityMetaComplete
&& containsAnnotation( superClassElement, Constants.ENTITY, Constants.MAPPED_SUPERCLASS ) ) {
return true;
}
return false;
} }
static class EmbeddedAttributeVisitor extends SimpleTypeVisitor8<@Nullable String, Element> { static class EmbeddedAttributeVisitor extends SimpleTypeVisitor8<@Nullable String, Element> {

View File

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

View File

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

View File

@ -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<Book> books1(@Pattern String title);
@HQL("where title like :title")
List<Book> books2(String title);
}

View File

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