HHH-16633 add DAO-style repository generation

This commit is contained in:
Gavin King 2023-07-07 14:31:39 +02:00
parent d83f472e18
commit 3969c74963
12 changed files with 292 additions and 34 deletions

View File

@ -15,6 +15,7 @@ import java.util.Date;
import java.util.List;
import javax.annotation.processing.FilerException;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
@ -99,11 +100,11 @@ public final class ClassWriter {
try ( PrintWriter pw = new PrintWriter(sw) ) {
if ( entity.getElement() instanceof TypeElement ) {
pw.println( writeStaticMetaModelAnnotation(entity) );
pw.println( writeStaticMetaModelAnnotation( entity ) );
}
if ( context.addGeneratedAnnotation() ) {
pw.println( writeGeneratedAnnotation(entity, context) );
pw.println( writeGeneratedAnnotation( entity, context ) );
}
if ( context.isAddSuppressWarningsAnnotation() ) {
pw.println( writeSuppressWarnings() );
@ -117,12 +118,12 @@ public final class ClassWriter {
for ( MetaAttribute metaMember : members ) {
if ( metaMember.hasTypedAttribute() ) {
metaMember.getAttributeDeclarationString().lines()
.forEach(line -> pw.println(" " + line));
.forEach(line -> pw.println('\t' + line));
}
}
pw.println();
for ( MetaAttribute metaMember : members ) {
pw.println( " " + metaMember.getAttributeNameDeclarationString() );
pw.println( '\t' + metaMember.getAttributeNameDeclarationString() );
}
pw.println();
@ -132,12 +133,17 @@ public final class ClassWriter {
}
private static void printClassDeclaration(Metamodel entity, PrintWriter pw, Context context) {
pw.print( "public abstract class " + entity.getSimpleName() + META_MODEL_CLASS_NAME_SUFFIX );
pw.print( entity.isImplementation() ? "public class " : "public abstract class " );
pw.print( entity.getSimpleName() + META_MODEL_CLASS_NAME_SUFFIX );
String superClassName = findMappedSuperClass( entity, context );
if ( superClassName != null ) {
pw.print( " extends " + superClassName + META_MODEL_CLASS_NAME_SUFFIX );
}
if ( entity.isImplementation() ) {
pw.print( entity.getElement().getKind() == ElementKind.CLASS ? " extends " : " implements " );
pw.print( entity.getSimpleName() );
}
pw.println( " {" );
}

View File

@ -62,6 +62,7 @@ public final class Context {
* place.
*/
private Boolean fullyXmlConfigured;
private boolean addInjectAnnotation = false;
private boolean addGeneratedAnnotation = true;
private boolean addGenerationDate;
private boolean addSuppressWarningsAnnotation;
@ -106,6 +107,14 @@ public final class Context {
return pe;
}
public boolean addInjectAnnotation() {
return addInjectAnnotation;
}
public void setAddInjectAnnotation(boolean addInjectAnnotation) {
this.addInjectAnnotation = addInjectAnnotation;
}
public boolean addGeneratedAnnotation() {
return addGeneratedAnnotation;
}

View File

@ -97,6 +97,11 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
Diagnostic.Kind.NOTE, "Hibernate JPA 2 Static-Metamodel Generator " + Version.getVersionString()
);
PackageElement jakartaInjectPackage =
context.getProcessingEnvironment().getElementUtils()
.getPackageElement( "jakarta.inject" );
context.setAddInjectAnnotation( jakartaInjectPackage != null );
String tmp = env.getOptions().get( JPAMetaModelEntityProcessor.ADD_GENERATED_ANNOTATION );
if ( tmp != null ) {
boolean addGeneratedAnnotation = Boolean.parseBoolean( tmp );

View File

@ -13,19 +13,23 @@ import java.util.List;
import java.util.Map;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import jakarta.persistence.EntityManager;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.Session;
import org.hibernate.StatelessSession;
import org.hibernate.jpamodelgen.Context;
import org.hibernate.jpamodelgen.ImportContextImpl;
import org.hibernate.jpamodelgen.model.ImportContext;
@ -39,6 +43,8 @@ import org.hibernate.jpamodelgen.validation.Validation;
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toList;
import static javax.lang.model.util.ElementFilter.fieldsIn;
import static javax.lang.model.util.ElementFilter.methodsIn;
import static org.hibernate.jpamodelgen.annotation.QueryMethod.isOrderParam;
import static org.hibernate.jpamodelgen.annotation.QueryMethod.isPageParam;
import static org.hibernate.jpamodelgen.util.NullnessUtil.castNonNull;
@ -85,6 +91,11 @@ public class AnnotationMetaEntity extends AnnotationMeta {
*/
private Metamodel entityToMerge;
/**
* True if this "metamodel class" is actually an instantiable DAO-style repository.
*/
private boolean dao;
public AnnotationMetaEntity(TypeElement element, Context context) {
this.element = element;
this.context = context;
@ -109,6 +120,11 @@ public class AnnotationMetaEntity extends AnnotationMeta {
return context;
}
@Override
public boolean isImplementation() {
return dao;
}
@Override
public final String getSimpleName() {
return element.getSimpleName().toString();
@ -209,17 +225,25 @@ public class AnnotationMetaEntity extends AnnotationMeta {
determineAccessTypeForHierarchy( element, context );
entityAccessTypeInfo = castNonNull( context.getAccessTypeInfo( getQualifiedName() ) );
final List<? extends Element> fieldsOfClass = ElementFilter.fieldsIn( element.getEnclosedElements() );
final List<? extends Element> methodsOfClass = ElementFilter.methodsIn( element.getEnclosedElements() );
final List<Element> gettersAndSettersOfClass = new ArrayList<>();
final List<VariableElement> fieldsOfClass = fieldsIn( element.getEnclosedElements() );
final List<ExecutableElement> methodsOfClass = methodsIn( element.getEnclosedElements() );
final List<ExecutableElement> gettersAndSettersOfClass = new ArrayList<>();
final List<ExecutableElement> queryMethods = new ArrayList<>();
for ( Element rawMethodOfClass: methodsOfClass ) {
for ( ExecutableElement rawMethodOfClass: methodsOfClass ) {
if ( isGetterOrSetter( rawMethodOfClass ) ) {
gettersAndSettersOfClass.add( rawMethodOfClass );
}
else if ( rawMethodOfClass instanceof ExecutableElement
&& containsAnnotation( rawMethodOfClass, Constants.HQL, Constants.SQL ) ) {
queryMethods.add( (ExecutableElement) rawMethodOfClass );
else if ( containsAnnotation( rawMethodOfClass, Constants.HQL, Constants.SQL ) ) {
queryMethods.add( rawMethodOfClass );
}
}
if ( !containsAnnotation( element, Constants.ENTITY ) ) {
for ( ExecutableElement getterOrSetter : gettersAndSettersOfClass ) {
if ( isSessionGetter( getterOrSetter ) ) {
dao = true;
addDaoConstructor( getterOrSetter );
}
}
}
@ -235,6 +259,29 @@ public class AnnotationMetaEntity extends AnnotationMeta {
initialized = true;
}
private void addDaoConstructor(ExecutableElement getterOrSetter) {
String name = getterOrSetter.getSimpleName().toString();
String typeName = element.getSimpleName().toString() + '_';
String type = getterOrSetter.getReturnType().toString();
putMember( name, new DaoConstructor(this, typeName, name, type, context.addInjectAnnotation() ) );
}
private static boolean isSessionGetter(ExecutableElement getterOrSetter) {
final TypeMirror type = getterOrSetter.getReturnType();
if ( type.getKind() == TypeKind.DECLARED ) {
final Element element = ((DeclaredType) type).asElement();
if ( element.getKind() == ElementKind.INTERFACE ) {
final Name name = ((TypeElement) element).getQualifiedName();
if ( name.contentEquals( Session.class.getName() )
|| name.contentEquals( EntityManager.class.getName() )
|| name.contentEquals( StatelessSession.class.getName() ) ) {
return true;
}
}
}
return false;
}
/**
* Check if method respects Java Bean conventions for getter and setters.
*
@ -287,7 +334,9 @@ public class AnnotationMetaEntity extends AnnotationMeta {
private void addQueryMethods(List<ExecutableElement> queryMethods) {
for ( ExecutableElement method : queryMethods) {
addQueryMethod( method );
if ( method.getModifiers().contains(Modifier.ABSTRACT) ) {
addQueryMethod( method );
}
}
}
@ -300,7 +349,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
if ( typeArguments.size() == 0 ) {
final String typeName = declaredType.toString();
if ( containsAnnotation( declaredType.asElement(), Constants.ENTITY ) ) {
addQueryMethod(method, methodName, typeName, null);
addQueryMethod( method, methodName, typeName, null );
}
else {
if ( isLegalRawResultType( typeName ) ) {
@ -389,7 +438,8 @@ public class AnnotationMetaEntity extends AnnotationMeta {
containerTypeName,
paramNames,
paramTypes,
isNative
isNative,
dao
);
putMember( attribute.getPropertyName() + paramTypes, attribute );

View File

@ -51,6 +51,11 @@ public class AnnotationMetaPackage extends AnnotationMeta {
return context;
}
@Override
public boolean isImplementation() {
return false;
}
@Override
public final String getSimpleName() {
return element.getSimpleName().toString();

View File

@ -0,0 +1,82 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.annotation;
import org.hibernate.jpamodelgen.model.MetaAttribute;
import org.hibernate.jpamodelgen.model.Metamodel;
public class DaoConstructor implements MetaAttribute {
private final Metamodel annotationMetaEntity;
private final String constructorName;
private final String methodName;
private final String returnTypeName;
private final boolean inject;
public DaoConstructor(Metamodel annotationMetaEntity, String constructorName, String methodName, String returnTypeName, boolean inject) {
this.annotationMetaEntity = annotationMetaEntity;
this.constructorName = constructorName;
this.methodName = methodName;
this.returnTypeName = returnTypeName;
this.inject = inject;
}
@Override
public boolean hasTypedAttribute() {
return true;
}
@Override
public String getAttributeDeclarationString() {
return new StringBuilder()
.append(inject ? "\n@" + annotationMetaEntity.importType("jakarta.inject.Inject") : "")
.append("\npublic ")
.append(constructorName)
.append("(")
.append(annotationMetaEntity.importType(returnTypeName))
.append(" entityManager) {")
.append("\n\tthis.entityManager = entityManager;")
.append("\n}")
.append("\n\n")
.append("public ")
.append(annotationMetaEntity.importType(returnTypeName))
.append(" ")
.append(methodName)
.append("() {")
.append("\n\treturn entityManager;")
.append("\n}")
.toString();
}
@Override
public String getAttributeNameDeclarationString() {
return new StringBuilder()
.append("\n\tprivate final ")
.append(annotationMetaEntity.importType(returnTypeName))
.append(" entityManager;")
.toString();
}
@Override
public String getMetaType() {
throw new UnsupportedOperationException();
}
@Override
public String getPropertyName() {
return methodName;
}
@Override
public String getTypeDeclaration() {
return "jakarta.persistence.EntityManager";
}
@Override
public Metamodel getHostingEntity() {
return annotationMetaEntity;
}
}

View File

@ -32,6 +32,7 @@ public class QueryMethod implements MetaAttribute {
private final List<String> paramNames;
private final List<String> paramTypes;
private final boolean isNative;
private final boolean belongsToDao;
public QueryMethod(
Metamodel annotationMetaEntity,
@ -43,7 +44,8 @@ public class QueryMethod implements MetaAttribute {
String containerTypeName,
List<String> paramNames,
List<String> paramTypes,
boolean isNative) {
boolean isNative,
boolean belongsToDao) {
this.annotationMetaEntity = annotationMetaEntity;
this.methodName = methodName;
this.queryString = queryString;
@ -52,6 +54,7 @@ public class QueryMethod implements MetaAttribute {
this.paramNames = paramNames;
this.paramTypes = paramTypes;
this.isNative = isNative;
this.belongsToDao = belongsToDao;
}
@Override
@ -74,12 +77,23 @@ public class QueryMethod implements MetaAttribute {
.append(join(",", paramTypes.stream().map(this::strip).map(annotationMetaEntity::importType).toArray()))
.append(")")
.append("\n **/\n");
if ( paramTypes.stream().anyMatch(ptype -> ptype.endsWith("..."))) {
boolean hasVarargs = paramTypes.stream().anyMatch(ptype -> ptype.endsWith("..."));
if ( hasVarargs ) {
declaration
.append("@SafeVarargs\n");
}
declaration
.append("public static ");
if ( belongsToDao ) {
declaration
.append("@Override\npublic ");
if ( hasVarargs ) {
declaration
.append("final ");
}
}
else {
declaration
.append("public static ");
}
StringBuilder type = new StringBuilder();
if (containerTypeName != null) {
type.append(annotationMetaEntity.importType(containerTypeName));
@ -94,18 +108,26 @@ public class QueryMethod implements MetaAttribute {
.append(type)
.append(" ")
.append(methodName)
.append("(")
.append(annotationMetaEntity.importType("jakarta.persistence.EntityManager"))
.append(" entityManager");
.append("(");
if ( !belongsToDao ) {
declaration
.append(annotationMetaEntity.importType("jakarta.persistence.EntityManager"))
.append(" entityManager");
}
for (int i =0; i<paramNames.size(); i++) {
for (int i = 0; i<paramNames.size(); i++ ) {
String ptype = paramTypes.get(i);
String param = paramNames.get(i);
String rptype = returnTypeName != null
? ptype.replace(returnTypeName, annotationMetaEntity.importType(returnTypeName))
: ptype;
if ( !belongsToDao || i>0 ) {
declaration
.append(", ");
}
declaration
.append(", ")
.append(annotationMetaEntity.importType(rptype))
.append(" ")
.append(param);
@ -113,7 +135,7 @@ public class QueryMethod implements MetaAttribute {
declaration
.append(")")
.append(" {")
.append("\n return ");
.append("\n\treturn ");
if ( isNative && returnTypeName != null
|| containerTypeName != null && containerTypeName.startsWith("org.hibernate") ) {
declaration.append("(").append(type).append(") ");
@ -136,7 +158,7 @@ public class QueryMethod implements MetaAttribute {
String ptype = paramTypes.get(i-1);
if (queryString.contains(":" + param)) {
declaration
.append("\n .setParameter(\"")
.append("\n\t\t\t.setParameter(\"")
.append(param)
.append("\", ")
.append(param)
@ -144,7 +166,7 @@ public class QueryMethod implements MetaAttribute {
}
else if (queryString.contains("?" + i)) {
declaration
.append("\n .setParameter(")
.append("\n\t\t\t.setParameter(")
.append(i)
.append(", ")
.append(param)
@ -154,7 +176,7 @@ public class QueryMethod implements MetaAttribute {
unwrap( declaration, unwrapped );
unwrapped = true;
declaration
.append("\n .setPage(")
.append("\n\t\t\t.setPage(")
.append(param)
.append(")");
}
@ -163,7 +185,7 @@ public class QueryMethod implements MetaAttribute {
unwrapped = true;
if (ptype.endsWith("...")) {
declaration
.append("\n .setOrder(")
.append("\n\t\t\t.setOrder(")
.append(annotationMetaEntity.importType(List.class.getName()))
.append(".of(")
.append(param)
@ -171,17 +193,17 @@ public class QueryMethod implements MetaAttribute {
}
else {
declaration
.append("\n .setOrder(")
.append("\n\t\t\t.setOrder(")
.append(param)
.append(")");
}
}
}
if ( containerTypeName == null) {
declaration.append("\n .getSingleResult()");
declaration.append("\n\t\t\t.getSingleResult()");
}
else if ( containerTypeName.equals("java.util.List") ) {
declaration.append("\n .getResultList()");
declaration.append("\n\t\t\t.getResultList()");
}
declaration.append(";\n}");
return declaration.toString();
@ -205,7 +227,7 @@ public class QueryMethod implements MetaAttribute {
private void unwrap(StringBuilder declaration, boolean unwrapped) {
if ( !unwrapped ) {
declaration
.append("\n .unwrap(")
.append("\n\t\t\t.unwrap(")
.append(annotationMetaEntity.importType(SelectionQuery.class.getName()))
.append(".class)");
}

View File

@ -34,4 +34,6 @@ public interface Metamodel extends ImportContext {
boolean isMetaComplete();
Context getContext();
boolean isImplementation();
}

View File

@ -632,4 +632,9 @@ public class XmlMetaEntity implements Metamodel {
public Context getContext() {
return context;
}
@Override
public boolean isImplementation() {
return false;
}
}

View File

@ -0,0 +1,11 @@
package org.hibernate.jpamodelgen.test.dao;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class Book {
@Id String isbn;
String title;
String text;
}

View File

@ -0,0 +1,34 @@
package org.hibernate.jpamodelgen.test.dao;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import org.hibernate.annotations.processing.HQL;
import org.hibernate.annotations.processing.SQL;
import java.util.List;
public interface Dao {
EntityManager getEntityManager();
@HQL("from Book where title like ?1")
TypedQuery<Book> findByTitle(String title);
@HQL("from Book where title like ?1 order by title fetch first ?2 rows only")
List<Book> findFirstNByTitle(String title, int N);
//
// @HQL("from Book where title like :title")
// List<Book> findByTitleWithPagination(String title, Order<? super Book> order, Page page);
//
// @HQL("from Book where title like :title")
// SelectionQuery<Book> findByTitleWithOrdering(String title, List<Order<? super Book>> order);
//
// @HQL("from Book where title like :title")
// SelectionQuery<Book> findByTitleWithOrderingByVarargs(String title, Order<? super Book>... order);
@HQL("from Book where isbn = :isbn")
Book findByIsbn(String isbn);
@SQL("select * from Book where isbn = :isbn")
Book findByIsbnNative(String isbn);
}

View File

@ -0,0 +1,27 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.test.dao;
import org.hibernate.jpamodelgen.test.util.CompilationTest;
import org.hibernate.jpamodelgen.test.util.TestUtil;
import org.hibernate.jpamodelgen.test.util.WithClasses;
import org.junit.Test;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertMetamodelClassGeneratedFor;
/**
* @author Gavin King
*/
public class DaoTest extends CompilationTest {
@Test
@WithClasses({ Book.class, Dao.class })
public void testGeneratedAnnotationNotGenerated() {
System.out.println( TestUtil.getMetaModelSourceAsString( Dao.class ) );
assertMetamodelClassGeneratedFor( Book.class );
assertMetamodelClassGeneratedFor( Dao.class );
}
}