HHH-16633 add ability to generate @Find methods

This commit is contained in:
Gavin King 2023-07-07 18:14:49 +02:00
parent 3969c74963
commit 0c40711563
13 changed files with 310 additions and 45 deletions

View File

@ -0,0 +1,28 @@
/*
* 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.annotations.processing;
import org.hibernate.Incubating;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* Identifies a method of an abstract class or interface as defining
* the signature of a finder method, and is generated automatically by
* the Hibernate Metamodel Generator.
*
* @author Gavin King
* @since 6.3
*/
@Target(METHOD)
@Retention(CLASS)
@Incubating
public @interface Find {}

View File

@ -30,6 +30,8 @@ import org.hibernate.jpamodelgen.util.TypeUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation;
/**
* Helper class to write the actual meta model class using the {@link javax.annotation.processing.Filer} API.
*
@ -123,8 +125,10 @@ public final class ClassWriter {
}
pw.println();
for ( MetaAttribute metaMember : members ) {
if ( metaMember.hasStringAttribute() ) {
pw.println( '\t' + metaMember.getAttributeNameDeclarationString() );
}
}
pw.println();
pw.println("}");
@ -190,8 +194,8 @@ public final class ClassWriter {
// 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 && ( TypeUtils.containsAnnotation( superClassElement, Constants.ENTITY )
|| TypeUtils.containsAnnotation( superClassElement, Constants.MAPPED_SUPERCLASS ) ) ) {
if ( !entityMetaComplete && ( containsAnnotation( superClassElement, Constants.ENTITY )
|| containsAnnotation( superClassElement, Constants.MAPPED_SUPERCLASS ) ) ) {
return true;
}

View File

@ -24,7 +24,6 @@ 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.lang.model.util.SimpleTypeVisitor8;
import javax.tools.Diagnostic;
@ -38,6 +37,8 @@ import org.hibernate.jpamodelgen.xml.JpaDescriptorParser;
import org.checkerframework.checker.nullness.qual.Nullable;
import static javax.lang.model.util.ElementFilter.fieldsIn;
import static javax.lang.model.util.ElementFilter.methodsIn;
import static org.hibernate.jpamodelgen.util.Constants.HQL;
import static org.hibernate.jpamodelgen.util.Constants.SQL;
import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation;
@ -56,6 +57,7 @@ import static org.hibernate.jpamodelgen.util.TypeUtils.isAnnotationMirrorOfType;
Constants.EMBEDDABLE,
Constants.HQL,
Constants.SQL,
Constants.FIND,
Constants.NAMED_QUERY,
Constants.NAMED_NATIVE_QUERY,
Constants.NAMED_ENTITY_GRAPH,
@ -247,7 +249,7 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
if ( entity.equals( containedEntity ) ) {
continue;
}
for ( Element subElement : ElementFilter.fieldsIn( entity.getElement().getEnclosedElements() ) ) {
for ( Element subElement : fieldsIn( entity.getElement().getEnclosedElements() ) ) {
TypeMirror mirror = subElement.asType();
if ( TypeKind.DECLARED == mirror.getKind() ) {
if ( mirror.accept( visitor, subElement ) ) {
@ -255,7 +257,7 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
}
}
}
for ( Element subElement : ElementFilter.methodsIn( entity.getElement().getEnclosedElements() ) ) {
for ( Element subElement : methodsIn( entity.getElement().getEnclosedElements() ) ) {
TypeMirror mirror = subElement.asType();
if ( TypeKind.DECLARED == mirror.getKind() ) {
if ( mirror.accept( visitor, subElement ) ) {

View File

@ -39,6 +39,11 @@ public abstract class AnnotationMetaAttribute implements MetaAttribute {
return true;
}
@Override
public boolean hasStringAttribute() {
return true;
}
@Override
public String getAttributeDeclarationString() {
return new StringBuilder()

View File

@ -233,7 +233,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
if ( isGetterOrSetter( rawMethodOfClass ) ) {
gettersAndSettersOfClass.add( rawMethodOfClass );
}
else if ( containsAnnotation( rawMethodOfClass, Constants.HQL, Constants.SQL ) ) {
else if ( containsAnnotation( rawMethodOfClass, Constants.HQL, Constants.SQL, Constants.FIND ) ) {
queryMethods.add( rawMethodOfClass );
}
}
@ -341,35 +341,33 @@ public class AnnotationMetaEntity extends AnnotationMeta {
}
private void addQueryMethod(ExecutableElement method) {
final String methodName = method.getSimpleName().toString();
final TypeMirror returnType = method.getReturnType();
if ( returnType instanceof DeclaredType ) {
if ( returnType.getKind() == TypeKind.DECLARED ) {
final DeclaredType declaredType = (DeclaredType) returnType;
final TypeElement typeElement = (TypeElement) declaredType.asElement();
final List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if ( typeArguments.size() == 0 ) {
final String typeName = declaredType.toString();
if ( containsAnnotation( declaredType.asElement(), Constants.ENTITY ) ) {
addQueryMethod( method, methodName, typeName, null );
addQueryMethod( method, declaredType, null );
}
else {
if ( isLegalRawResultType( typeName ) ) {
addQueryMethod( method, methodName, null, typeName );
if ( isLegalRawResultType( typeElement.getQualifiedName().toString() ) ) {
addQueryMethod( method, null, typeElement );
}
else {
// probably a projection
addQueryMethod( method, methodName, typeName, null );
addQueryMethod( method, declaredType, null );
}
}
}
else if ( typeArguments.size() == 1 ) {
final String containerTypeName = declaredType.asElement().toString();
final String returnTypeName = typeArguments.get(0).toString();
if ( isLegalGenericResultType( containerTypeName ) ) {
addQueryMethod( method, methodName, returnTypeName, containerTypeName );
final Element containerType = declaredType.asElement();
if ( isLegalGenericResultType( containerType.toString() ) ) {
addQueryMethod( method, typeArguments.get(0), typeElement );
}
else {
context.message( method,
"incorrect return type '" + containerTypeName + "'",
"incorrect return type '" + containerType + "'",
Diagnostic.Kind.ERROR );
}
}
@ -396,46 +394,121 @@ public class AnnotationMetaEntity extends AnnotationMeta {
private void addQueryMethod(
ExecutableElement method,
String methodName,
@Nullable String returnTypeName,
@Nullable String containerTypeName) {
@Nullable TypeMirror returnType,
@Nullable TypeElement containerType) {
final AnnotationMirror hql = getAnnotationMirror( method, Constants.HQL );
if ( hql != null ) {
addQueryMethod( method, methodName, returnTypeName, containerTypeName, hql, false );
addQueryMethod( method, returnType, containerType, hql, false );
}
final AnnotationMirror sql = getAnnotationMirror( method, Constants.SQL );
if ( sql != null ) {
addQueryMethod( method, methodName, returnTypeName, containerTypeName, sql, true );
addQueryMethod( method, returnType, containerType, sql, true );
}
final AnnotationMirror find = getAnnotationMirror( method, Constants.FIND );
if ( find != null ) {
addFinderMethod( method, returnType, containerType );
}
}
private void addFinderMethod(
ExecutableElement method,
@Nullable TypeMirror returnType,
@Nullable TypeElement containerType) {
if ( containerType != null ) {
context.message( method,
"incorrect return type '" + containerType.getQualifiedName() + "' is not an entity type",
Diagnostic.Kind.ERROR );
}
else if ( returnType == null || returnType.getKind() != TypeKind.DECLARED ) {
context.message( method,
"incorrect return type '" + returnType + "' is not an entity type",
Diagnostic.Kind.ERROR );
}
else {
final DeclaredType declaredType = (DeclaredType) returnType;
final TypeElement entity = (TypeElement) declaredType.asElement();
if ( !containsAnnotation( entity, Constants.ENTITY ) ) {
context.message( method,
"incorrect return type '" + returnType + "' is not annotated '@Entity'",
Diagnostic.Kind.ERROR );
}
else {
switch ( method.getParameters().size() ) {
case 0:
context.message( method,
"missing parameter",
Diagnostic.Kind.ERROR );
break;
case 1:
final VariableElement parameter = method.getParameters().get(0);
validateFinderParameter( entity, parameter);
final String methodName = method.getSimpleName().toString();
putMember( methodName,
new FinderMethod(
this,
methodName,
returnType.toString(),
parameter.getSimpleName().toString(),
parameter.asType().toString(),
dao
)
);
break;
default:
context.message( method,
"too many parameters ('@IdClass' not yet supported)",
Diagnostic.Kind.ERROR );
}
}
}
}
private void validateFinderParameter(TypeElement entity, VariableElement param) {
entity.getEnclosedElements().stream()
.filter(member -> member.getSimpleName().contentEquals( param.getSimpleName() )
&& member.getAnnotationMirrors().stream()
.anyMatch(annotation -> {
final TypeElement annotationType = (TypeElement)
annotation.getAnnotationType().asElement();
final Name annotatioName = annotationType.getQualifiedName();
return annotatioName.contentEquals(Constants.ID)
|| annotatioName.contentEquals(Constants.EMBEDDED_ID);
}))
.findAny()
.ifPresentOrElse(
member -> {
final String memberType = member.asType().toString();
final String paramType = param.asType().toString();
if ( !memberType.equals(paramType)) {
context.message( param,
"matching '@Id' field in entity class has type '" + memberType + "'",
Diagnostic.Kind.ERROR );
}
},
() -> context.message( param,
"no matching '@Id' field in entity class",
Diagnostic.Kind.ERROR )
);
}
private void addQueryMethod(
ExecutableElement method,
String methodName,
@Nullable
String returnTypeName,
@Nullable
String containerTypeName,
@Nullable TypeMirror returnType,
@Nullable TypeElement containerType,
AnnotationMirror mirror,
boolean isNative) {
final Object queryString = getAnnotationValue( mirror, "value" );
if ( queryString instanceof String ) {
final List<String> paramNames =
method.getParameters().stream()
.map( param -> param.getSimpleName().toString() )
.collect( toList() );
final List<String> paramTypes =
method.getParameters().stream()
.map( param -> param.asType().toString() )
.collect( toList() );
final List<String> paramNames = parameterNames( method );
final List<String> paramTypes = parameterTypes( method );
final String hql = (String) queryString;
final QueryMethod attribute =
new QueryMethod(
this,
methodName,
method.getSimpleName().toString(),
hql,
returnTypeName,
containerTypeName,
returnType == null ? null : returnType.toString(),
containerType == null ? null : containerType.getQualifiedName().toString(),
paramNames,
paramTypes,
isNative,
@ -457,6 +530,18 @@ public class AnnotationMetaEntity extends AnnotationMeta {
}
}
private static List<String> parameterTypes(ExecutableElement method) {
return method.getParameters().stream()
.map(param -> param.asType().toString())
.collect(toList());
}
private static List<String> parameterNames(ExecutableElement method) {
return method.getParameters().stream()
.map(param -> param.getSimpleName().toString())
.collect(toList());
}
private void checkParameters(ExecutableElement method, List<String> paramNames, List<String> paramTypes, AnnotationMirror mirror, String hql) {
for (int i = 1; i <= paramNames.size(); i++) {
final String param = paramNames.get(i-1);

View File

@ -9,6 +9,9 @@ package org.hibernate.jpamodelgen.annotation;
import org.hibernate.jpamodelgen.model.MetaAttribute;
import org.hibernate.jpamodelgen.model.Metamodel;
/**
* @author Gavin King
*/
public class DaoConstructor implements MetaAttribute {
private final Metamodel annotationMetaEntity;
private final String constructorName;
@ -29,9 +32,18 @@ public class DaoConstructor implements MetaAttribute {
return true;
}
@Override
public boolean hasStringAttribute() {
return false;
}
@Override
public String getAttributeDeclarationString() {
return new StringBuilder()
.append("\nprivate final ")
.append(annotationMetaEntity.importType(returnTypeName))
.append(" entityManager;")
.append("\n")
.append(inject ? "\n@" + annotationMetaEntity.importType("jakarta.inject.Inject") : "")
.append("\npublic ")
.append(constructorName)
@ -53,11 +65,7 @@ public class DaoConstructor implements MetaAttribute {
@Override
public String getAttributeNameDeclarationString() {
return new StringBuilder()
.append("\n\tprivate final ")
.append(annotationMetaEntity.importType(returnTypeName))
.append(" entityManager;")
.toString();
throw new UnsupportedOperationException();
}
@Override

View File

@ -0,0 +1,111 @@
/*
* 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;
/**
* @author Gavin King
*/
public class FinderMethod implements MetaAttribute {
private final Metamodel annotationMetaEntity;
private final String methodName;
private final String entity;
private final String paramName;
private final String paramType;
private final boolean belongsToDao;
public FinderMethod(Metamodel annotationMetaEntity, String methodName, String entity, String paramName, String paramType, boolean belongsToDao) {
this.annotationMetaEntity = annotationMetaEntity;
this.methodName = methodName;
this.entity = entity;
this.paramName = paramName;
this.paramType = paramType;
this.belongsToDao = belongsToDao;
}
@Override
public boolean hasTypedAttribute() {
return true;
}
@Override
public boolean hasStringAttribute() {
return false;
}
@Override
public String getAttributeDeclarationString() {
StringBuilder declaration = new StringBuilder();
declaration
.append("\n/**\n * @see ")
.append(annotationMetaEntity.getQualifiedName())
.append("#")
.append(methodName)
.append("(")
.append(annotationMetaEntity.importType(paramType))
.append(")")
.append("\n **/\n");
if ( belongsToDao ) {
declaration
.append("@Override\npublic ");
}
else {
declaration
.append("public static ");
}
declaration
.append(annotationMetaEntity.importType(entity));
declaration
.append(" ")
.append(methodName)
.append("(");
if ( !belongsToDao ) {
declaration
.append(annotationMetaEntity.importType("jakarta.persistence.EntityManager"))
.append(" entityManager, ");
}
declaration
.append(annotationMetaEntity.importType(paramType))
.append(" ")
.append(paramName)
.append(") {")
.append("\n\treturn entityManager.find(")
.append(annotationMetaEntity.importType(entity))
.append(".class, ")
.append(paramName)
.append(");")
.append("\n}");
return declaration.toString();
}
@Override
public String getAttributeNameDeclarationString() {
throw new UnsupportedOperationException();
}
@Override
public String getMetaType() {
throw new UnsupportedOperationException();
}
@Override
public String getPropertyName() {
return methodName;
}
@Override
public String getTypeDeclaration() {
return entity;
}
@Override
public Metamodel getHostingEntity() {
return annotationMetaEntity;
}
}

View File

@ -29,6 +29,11 @@ class NameMetaAttribute implements MetaAttribute {
return false;
}
@Override
public boolean hasStringAttribute() {
return true;
}
@Override
public String getAttributeDeclarationString() {
throw new UnsupportedOperationException();

View File

@ -62,6 +62,11 @@ public class QueryMethod implements MetaAttribute {
return true;
}
@Override
public boolean hasStringAttribute() {
return true;
}
@Override
public String getAttributeDeclarationString() {
List<String> paramTypes = this.paramTypes.stream()

View File

@ -13,6 +13,8 @@ public interface MetaAttribute {
boolean hasTypedAttribute();
boolean hasStringAttribute();
String getAttributeDeclarationString();
String getAttributeNameDeclarationString();

View File

@ -53,6 +53,7 @@ public final class Constants {
public static final String HQL = "org.hibernate.annotations.processing.HQL";
public static final String SQL = "org.hibernate.annotations.processing.SQL";
public static final String FIND = "org.hibernate.annotations.processing.Find";
public static final String CHECK_HQL = "org.hibernate.annotations.processing.CheckHQL";

View File

@ -30,6 +30,11 @@ public abstract class XmlMetaAttribute implements MetaAttribute {
return true;
}
@Override
public boolean hasStringAttribute() {
return true;
}
@Override
public String getAttributeDeclarationString() {
return "public static volatile " + hostingEntity.importType( getMetaType() )

View File

@ -2,6 +2,7 @@ package org.hibernate.jpamodelgen.test.dao;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;
import org.hibernate.annotations.processing.SQL;
@ -11,6 +12,9 @@ public interface Dao {
EntityManager getEntityManager();
@Find
Book getBook(String isbn);
@HQL("from Book where title like ?1")
TypedQuery<Book> findByTitle(String title);