From 4cfc3fb97de19ffdf1fd66dfbe1b655fe7812b98 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 23 Feb 2024 13:38:44 +0100 Subject: [PATCH] HHH-17772 support Jakarta Data @OrderBy --- .../annotation/AnnotationMetaEntity.java | 59 +++++++++++++++---- .../annotation/CriteriaFinderMethod.java | 48 ++++++++++++++- 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java index 49ca2e05d5..b7aff23992 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java @@ -36,6 +36,7 @@ import org.hibernate.jpamodelgen.Context; import org.hibernate.jpamodelgen.ImportContextImpl; import org.hibernate.jpamodelgen.ProcessLaterException; +import org.hibernate.jpamodelgen.annotation.CriteriaFinderMethod.OrderBy; import org.hibernate.jpamodelgen.model.ImportContext; import org.hibernate.jpamodelgen.model.MetaAttribute; import org.hibernate.jpamodelgen.model.Metamodel; @@ -733,11 +734,48 @@ else if ( !context.getTypeUtils().isSameType( typeArgument, entity.asType() ) ) sessionType[0], sessionType[1], enabledFetchProfiles( method ), + orderByList( method, entity ), context.addNonnullAnnotation() ) ); } + private List orderByList(ExecutableElement method, TypeElement entityType) { + final AnnotationMirror orderByList = + getAnnotationMirror( method, "jakarta.data.repository.OrderBy.List" ); + if ( orderByList != null ) { + final List result = new ArrayList<>(); + @SuppressWarnings("unchecked") + final List list = (List) + castNonNull( getAnnotationValue( orderByList, "value" ) ); + for ( AnnotationValue element : list ) { + result.add( orderByExpression( castNonNull( (AnnotationMirror) element.getValue() ), entityType, method ) ); + } + return result; + } + final AnnotationMirror orderBy = + getAnnotationMirror( method, "jakarta.data.repository.OrderBy" ); + if ( orderBy != null ) { + return List.of( orderByExpression(orderBy, entityType, method) ); + } + return emptyList(); + } + + private OrderBy orderByExpression(AnnotationMirror orderBy, TypeElement entityType, ExecutableElement method) { + final String fieldName = (String) castNonNull( getAnnotationValue(orderBy, "value") ); + final Boolean descendingOrNull = (Boolean) getAnnotationValue(orderBy, "descending"); + final Boolean ignoreCaseOrNull = (Boolean) getAnnotationValue(orderBy, "ignoreCase"); + final boolean descending = descendingOrNull != null && descendingOrNull; + final boolean ignoreCase = ignoreCaseOrNull != null && ignoreCaseOrNull; + if ( memberMatchingPath( entityType, fieldName ) == null ) { + context.message( method, orderBy, + "no matching field named '" + fieldName + + "' in entity class '" + entityType.getQualifiedName() + "'", + Diagnostic.Kind.ERROR ); + } + return new OrderBy( fieldName, descending, ignoreCase ); + } + private static @Nullable TypeMirror getTypeArgument(TypeMirror parameterType) { switch ( parameterType.getKind() ) { case ARRAY: @@ -865,6 +903,7 @@ && matchesNaturalKey( method, entity ) ) { sessionType[0], sessionType[1], enabledFetchProfiles( method ), + orderByList( method, entity ), context.addNonnullAnnotation() ) ); @@ -932,6 +971,7 @@ private void createSingleParameterFinder(ExecutableElement method, TypeMirror re sessionType[0], sessionType[1], profiles, + orderByList( method, entity ), context.addNonnullAnnotation() ) ); @@ -988,7 +1028,7 @@ private int countNaturalIdFields(TypeElement entity) { } private @Nullable FieldType validateFinderParameter(TypeElement entityType, VariableElement param) { - final Element member = memberMatchingParameter(entityType, param); + final Element member = memberMatchingPath( entityType, parameterName( param ) ); if ( member != null) { final String memberType = memberType( member ).toString(); final String paramType = param.asType().toString(); @@ -1027,7 +1067,7 @@ else if ( containsAnnotation( member, Constants.NATURAL_ID ) ) { } private boolean finderParameterNullable(TypeElement entity, VariableElement param) { - final Element member = memberMatchingParameter(entity, param); + final Element member = memberMatchingPath( entity, parameterName( param ) ); return member == null || isNullable(member); } @@ -1047,17 +1087,17 @@ private static TypeMirror memberType(Element member) { } } - private @Nullable Element memberMatchingParameter(TypeElement entityType, VariableElement param) { - final StringTokenizer tokens = new StringTokenizer( parameterName( param ), "$" ); - return memberMatchingParameter( entityType, param, tokens ); + private @Nullable Element memberMatchingPath(TypeElement entityType, String path) { + final StringTokenizer tokens = new StringTokenizer( path, "$" ); + return memberMatchingPath( entityType, tokens ); } - private @Nullable Element memberMatchingParameter(TypeElement entityType, VariableElement param, StringTokenizer tokens) { + private @Nullable Element memberMatchingPath(TypeElement entityType, StringTokenizer tokens) { final AccessType accessType = getAccessType(entityType); final String nextToken = tokens.nextToken(); for ( Element member : entityType.getEnclosedElements() ) { final Element match = - memberMatchingParameter(entityType, param, member, accessType, tokens, nextToken); + memberMatchingPath(entityType, member, accessType, tokens, nextToken); if ( match != null ) { return match; } @@ -1065,9 +1105,8 @@ private static TypeMirror memberType(Element member) { return null; } - private @Nullable Element memberMatchingParameter( + private @Nullable Element memberMatchingPath( TypeElement entityType, - VariableElement param, Element candidate, AccessType accessType, StringTokenizer tokens, @@ -1101,7 +1140,7 @@ else if ( accessType == AccessType.PROPERTY && candidate.getKind() == ElementKin final TypeElement memberType = (TypeElement) declaredType.asElement(); memberTypes.put( qualify( entityType.getQualifiedName().toString(), memberName.toString() ), memberType.getQualifiedName().toString() ); - return memberMatchingParameter( memberType, param, tokens ); + return memberMatchingPath( memberType, tokens ); } return null; } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/CriteriaFinderMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/CriteriaFinderMethod.java index d8498036c6..5425554b30 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/CriteriaFinderMethod.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/CriteriaFinderMethod.java @@ -21,8 +21,9 @@ public class CriteriaFinderMethod extends AbstractFinderMethod { private final @Nullable String containerType; private final List paramNullability; + private final List orderBys; - public CriteriaFinderMethod( + CriteriaFinderMethod( AnnotationMetaEntity annotationMetaEntity, String methodName, String entity, @Nullable String containerType, @@ -32,11 +33,13 @@ public CriteriaFinderMethod( String sessionType, String sessionName, List fetchProfiles, + List orderBys, boolean addNonnullAnnotation) { super( annotationMetaEntity, methodName, entity, belongsToDao, sessionType, sessionName, fetchProfiles, paramNames, paramTypes, addNonnullAnnotation ); this.containerType = containerType; this.paramNullability = paramNullability; + this.orderBys = orderBys; } @Override @@ -101,7 +104,38 @@ public String getAttributeDeclarationString() { } } declaration - .append("\n\t);") + .append("\n\t);"); + if ( !orderBys.isEmpty() ) { + declaration.append("\n\tquery.orderBy("); + boolean firstOrderBy = true; + for ( OrderBy orderBy : orderBys ) { + if ( firstOrderBy ) { + firstOrderBy = false; + } + else { + declaration.append(", "); + } + declaration + .append("builder.") + .append(orderBy.descending ? "desc" : "asc") + .append('('); + if ( orderBy.ignoreCase ) { + declaration.append("builder.lower("); + } + declaration + .append("entity.get(\"") + .append(orderBy.fieldName) + .append("\")"); + if ( orderBy.ignoreCase ) { + declaration + .append(')'); + } + declaration + .append(')'); + } + declaration.append(");"); + } + declaration .append("\n\treturn ") .append(sessionName) .append(".createQuery(query)"); @@ -207,4 +241,14 @@ private StringBuilder returnType() { return type; } + static class OrderBy { + String fieldName; + boolean descending; + boolean ignoreCase; + public OrderBy(String fieldName, boolean descending, boolean ignoreCase) { + this.fieldName = fieldName; + this.descending = descending; + this.ignoreCase = ignoreCase; + } + } }