From f7b895ba113d4a22ab37a49850dffadeb5761347 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 22 Mar 2024 13:48:49 +0100 Subject: [PATCH] HHH-17875 validate association mappings in processor --- .../annotation/AnnotationMetaEntity.java | 140 +++++++++++------- 1 file changed, 83 insertions(+), 57 deletions(-) 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 ce4ea5631f..5208b68758 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 @@ -762,67 +762,15 @@ public class AnnotationMetaEntity extends AnnotationMeta { case DECLARED: final DeclaredType assocDeclaredType = (DeclaredType) typeMirror; final TypeElement assocTypeElement = (TypeElement) assocDeclaredType.asElement(); - if ( !hasAnnotation(assocTypeElement, ENTITY) ) { + if ( hasAnnotation(assocTypeElement, ENTITY) ) { + final String mappedBy = (String) getAnnotationValue(annotation, "mappedBy"); + validateBidirectionalMapping(memberOfClass, annotation, mappedBy, assocTypeElement); + } + else { context.message(memberOfClass, "type '" + assocTypeElement.getSimpleName() + "' is not annotated '@Entity'", Diagnostic.Kind.WARNING); } - final String mappedBy = (String) getAnnotationValue(annotation, "mappedBy"); - if ( mappedBy != null && !mappedBy.isEmpty() ) { - if ( mappedBy.equals("") ) { - return; -// throw new ProcessLaterException(); - } - if ( mappedBy.indexOf('.')>0 ) { - //we don't know how to handle paths yet - return; - } - final List members = - context.getElementUtils().getAllMembers(assocTypeElement); - final AnnotationValue annotationVal = - castNonNull(getAnnotationValueRef(annotation, "mappedBy")); - if ( members.stream().noneMatch(m -> propertyName(this, m).contentEquals(mappedBy)) ) { - context.message(memberOfClass, annotation, - annotationVal, - "no matching member in '" + assocTypeElement.getSimpleName() + "'", - Diagnostic.Kind.ERROR); - } - else { - final Element member = - members.stream() - .filter(m -> propertyName(this, m).contentEquals(mappedBy)) - .findFirst().get(); - if ( hasAnnotation(member, MANY_TO_ONE) ) { - final TypeMirror backType = attributeType(member); - if ( !context.getTypeUtils().isSameType(backType, element.asType()) ) { - context.message(memberOfClass, annotation, annotationVal, - "member '" + member.getSimpleName() - + "' of '" + assocTypeElement.getSimpleName() - + "' is not of type '" + element.getSimpleName() + "'", - Diagnostic.Kind.WARNING); - } - } - else if ( hasAnnotation(member, MANY_TO_MANY) ) { - final TypeMirror backType = elementType( attributeType(member) ); - if ( backType != null ) { - if ( !context.getTypeUtils().isSameType(backType, element.asType()) ) { - context.message(memberOfClass, annotation, annotationVal, - "member '" + member.getSimpleName() - + "' of '" + assocTypeElement.getSimpleName() - + "' is not of type '" + element.getSimpleName() + "'", - Diagnostic.Kind.WARNING); - } - } - } - else { - context.message(memberOfClass, annotation, annotationVal, - "member '" + member.getSimpleName() - + "' of '" + assocTypeElement.getSimpleName() - + "' is not annotated '@ManyToMany' or '@ManyToOne'", - Diagnostic.Kind.WARNING); - } - } - } break; default: context.message(memberOfClass, "type '" + typeMirror + "' is not an entity type", @@ -831,6 +779,84 @@ public class AnnotationMetaEntity extends AnnotationMeta { } } + private void validateBidirectionalMapping( + Element memberOfClass, AnnotationMirror annotation, @Nullable String mappedBy, TypeElement assocTypeElement) { + if ( mappedBy != null && !mappedBy.isEmpty() ) { + if ( mappedBy.equals("") ) { + return; +// throw new ProcessLaterException(); + } + if ( mappedBy.indexOf('.')>0 ) { + //we don't know how to handle paths yet + return; + } + final AnnotationValue annotationVal = + castNonNull(getAnnotationValueRef(annotation, "mappedBy")); + for ( Element member : context.getElementUtils().getAllMembers(assocTypeElement) ) { + if ( propertyName(this, member).contentEquals(mappedBy) ) { + validateBackRef(memberOfClass, annotation, assocTypeElement, member, annotationVal); + return; + } + } + // not found + context.message(memberOfClass, annotation, + annotationVal, + "no matching member in '" + assocTypeElement.getSimpleName() + "'", + Diagnostic.Kind.ERROR); + } + } + + private void validateBackRef( + Element memberOfClass, + AnnotationMirror annotation, + TypeElement assocTypeElement, + Element member, + AnnotationValue annotationVal) { + final TypeMirror backType; + switch ( annotation.getAnnotationType().asElement().toString() ) { + case ONE_TO_ONE: + backType = attributeType(member); + if ( !hasAnnotation(member, ONE_TO_ONE) ) { + context.message(memberOfClass, annotation, annotationVal, + "member '" + member.getSimpleName() + + "' of '" + assocTypeElement.getSimpleName() + + "' is not annotated '@OneToOne'", + Diagnostic.Kind.WARNING); + } + break; + case ONE_TO_MANY: + backType = attributeType(member); + if ( !hasAnnotation(member, MANY_TO_ONE) ) { + context.message(memberOfClass, annotation, annotationVal, + "member '" + member.getSimpleName() + + "' of '" + assocTypeElement.getSimpleName() + + "' is not annotated '@ManyToOne'", + Diagnostic.Kind.WARNING); + } + break; + case MANY_TO_MANY: + backType = elementType( attributeType(member) ); + if ( !hasAnnotation(member, MANY_TO_MANY) ) { + context.message(memberOfClass, annotation, annotationVal, + "member '" + member.getSimpleName() + + "' of '" + assocTypeElement.getSimpleName() + + "' is not annotated '@ManyToMany'", + Diagnostic.Kind.WARNING); + } + break; + default: + throw new AssertionFailure("should not have a mappedBy"); + } + if ( backType!=null + && !context.getTypeUtils().isSameType(backType, element.asType()) ) { + context.message(memberOfClass, annotation, annotationVal, + "member '" + member.getSimpleName() + + "' of '" + assocTypeElement.getSimpleName() + + "' is not of type '" + element.getSimpleName() + "'", + Diagnostic.Kind.WARNING); + } + } + private boolean isPersistent(Element memberOfClass, AccessType membersKind) { return ( entityAccessTypeInfo.getAccessType() == membersKind || determineAnnotationSpecifiedAccessType( memberOfClass ) != null )