Skip to content

Commit 3457b2d

Browse files
committed
HHH-18826 mappedBy validation in Processor
tolerate a mappedBy which refers to a parent id field rather than an association Signed-off-by: Gavin King <[email protected]>
1 parent 356ea20 commit 3457b2d

File tree

1 file changed

+59
-49
lines changed

1 file changed

+59
-49
lines changed

tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import static org.hibernate.grammars.hql.HqlLexer.ORDER;
7676
import static org.hibernate.grammars.hql.HqlLexer.WHERE;
7777
import static org.hibernate.internal.util.StringHelper.qualify;
78+
import static org.hibernate.internal.util.StringHelper.unqualify;
7879
import static org.hibernate.processor.annotation.AbstractQueryMethod.isSessionParameter;
7980
import static org.hibernate.processor.annotation.AbstractQueryMethod.isSpecialParam;
8081
import static org.hibernate.processor.annotation.QueryMethod.isOrderParam;
@@ -966,8 +967,9 @@ private void validateAssociation(Element memberOfClass, AnnotationMirror annotat
966967
final TypeElement assocTypeElement = (TypeElement) assocDeclaredType.asElement();
967968
if ( hasAnnotation(assocTypeElement, ENTITY) ) {
968969
final AnnotationValue mappedBy = getAnnotationValue(annotation, "mappedBy");
969-
final String propertyName = mappedBy == null ? null : mappedBy.getValue().toString();
970-
validateBidirectionalMapping(memberOfClass, annotation, propertyName, assocTypeElement);
970+
if ( mappedBy != null ) {
971+
validateBidirectionalMapping(memberOfClass, annotation, mappedBy, assocTypeElement);
972+
}
971973
}
972974
else {
973975
message(memberOfClass, "type '" + assocTypeElement.getSimpleName()
@@ -983,30 +985,27 @@ private void validateAssociation(Element memberOfClass, AnnotationMirror annotat
983985
}
984986

985987
private void validateBidirectionalMapping(
986-
Element memberOfClass, AnnotationMirror annotation, @Nullable String mappedBy, TypeElement assocTypeElement) {
987-
if ( mappedBy != null && !mappedBy.isEmpty() ) {
988-
if ( mappedBy.equals("<error>") ) {
989-
return;
990-
// throw new ProcessLaterException();
991-
}
992-
if ( mappedBy.indexOf('.')>0 ) {
988+
Element memberOfClass, AnnotationMirror annotation, AnnotationValue annotationVal, TypeElement assocTypeElement) {
989+
final String mappedBy = annotationVal.getValue().toString();
990+
if ( mappedBy != null && !mappedBy.isEmpty()
991+
// this happens for a typesafe ref, e.g. Page_BOOK
992+
// TODO: we should queue it to validate it later somehow
993+
&& !mappedBy.equals( "<error>" ) ) {
994+
if ( mappedBy.indexOf( '.' ) > 0 ) {
993995
//we don't know how to handle paths yet
994996
return;
995997
}
996-
final AnnotationValue annotationVal =
997-
castNonNull(getAnnotationValue(annotation, "mappedBy"));
998-
for ( Element member : context.getAllMembers(assocTypeElement) ) {
999-
if ( propertyName(this, member).contentEquals(mappedBy)
1000-
&& compatibleAccess(assocTypeElement, member) ) {
1001-
validateBackRef(memberOfClass, annotation, assocTypeElement, member, annotationVal);
998+
for ( Element member : context.getAllMembers( assocTypeElement ) ) {
999+
if ( propertyName( this, member ).contentEquals( mappedBy )
1000+
&& compatibleAccess( assocTypeElement, member ) ) {
1001+
validateBackRef( memberOfClass, annotation, assocTypeElement, member, annotationVal );
10021002
return;
10031003
}
10041004
}
10051005
// not found
1006-
message(memberOfClass, annotation,
1007-
annotationVal,
1006+
message( memberOfClass, annotation, annotationVal,
10081007
"no matching member in '" + assocTypeElement.getSimpleName() + "'",
1009-
Diagnostic.Kind.ERROR);
1008+
Diagnostic.Kind.ERROR );
10101009
}
10111010
}
10121011

@@ -1024,51 +1023,62 @@ private void validateBackRef(
10241023
Element memberOfClass,
10251024
AnnotationMirror annotation,
10261025
TypeElement assocTypeElement,
1027-
Element member,
1026+
Element referencedMember,
10281027
AnnotationValue annotationVal) {
10291028
final TypeMirror backType;
1029+
final String expectedMappingAnnotation;
10301030
switch ( annotation.getAnnotationType().asElement().toString() ) {
10311031
case ONE_TO_ONE:
1032-
backType = attributeType(member);
1033-
if ( !hasAnnotation(member, ONE_TO_ONE) ) {
1034-
message(memberOfClass, annotation, annotationVal,
1035-
"member '" + member.getSimpleName()
1036-
+ "' of '" + assocTypeElement.getSimpleName()
1037-
+ "' is not annotated '@OneToOne'",
1038-
Diagnostic.Kind.WARNING);
1039-
}
1032+
backType = attributeType(referencedMember);
1033+
expectedMappingAnnotation = ONE_TO_ONE;
10401034
break;
10411035
case ONE_TO_MANY:
1042-
backType = attributeType(member);
1043-
if ( !hasAnnotation(member, MANY_TO_ONE) ) {
1044-
message(memberOfClass, annotation, annotationVal,
1045-
"member '" + member.getSimpleName()
1046-
+ "' of '" + assocTypeElement.getSimpleName()
1047-
+ "' is not annotated '@ManyToOne'",
1048-
Diagnostic.Kind.WARNING);
1049-
}
1036+
backType = attributeType(referencedMember);
1037+
expectedMappingAnnotation = MANY_TO_ONE;
10501038
break;
10511039
case MANY_TO_MANY:
1052-
backType = elementType( attributeType(member) );
1053-
if ( !hasAnnotation(member, MANY_TO_MANY) ) {
1040+
backType = elementType( attributeType(referencedMember) );
1041+
expectedMappingAnnotation = MANY_TO_MANY;
1042+
break;
1043+
default:
1044+
throw new AssertionFailure("should not have a mappedBy");
1045+
}
1046+
if ( backType != null ) {
1047+
final Element idMember = getIdMember();
1048+
final Types typeUtils = context.getTypeUtils();
1049+
if ( idMember != null && typeUtils.isSameType( backType, idMember.asType() ) ) {
1050+
// mappedBy references a regular field of the same type as the entity id
1051+
//TODO: any other validation to do here??
1052+
}
1053+
else if ( typeUtils.isSameType( backType, element.asType() ) ) {
1054+
// mappedBy references a field of the same type as the entity
1055+
// it needs to be mapped as the appropriate sort of association
1056+
if ( !hasAnnotation( referencedMember, expectedMappingAnnotation ) ) {
10541057
message(memberOfClass, annotation, annotationVal,
1055-
"member '" + member.getSimpleName()
1058+
"member '" + referencedMember.getSimpleName()
10561059
+ "' of '" + assocTypeElement.getSimpleName()
1057-
+ "' is not annotated '@ManyToMany'",
1060+
+ "' is not annotated '@" + unqualify(expectedMappingAnnotation) + "'",
10581061
Diagnostic.Kind.WARNING);
10591062
}
1060-
break;
1061-
default:
1062-
throw new AssertionFailure("should not have a mappedBy");
1063+
}
1064+
else {
1065+
// mappedBy references a field which seems to be of the wrong type
1066+
message( memberOfClass, annotation, annotationVal,
1067+
"member '" + referencedMember.getSimpleName()
1068+
+ "' of '" + assocTypeElement.getSimpleName()
1069+
+ "' is not of type '" + element.getSimpleName() + "'",
1070+
Diagnostic.Kind.WARNING );
1071+
}
10631072
}
1064-
if ( backType!=null
1065-
&& !context.getTypeUtils().isSameType(backType, element.asType()) ) {
1066-
message(memberOfClass, annotation, annotationVal,
1067-
"member '" + member.getSimpleName()
1068-
+ "' of '" + assocTypeElement.getSimpleName()
1069-
+ "' is not of type '" + element.getSimpleName() + "'",
1070-
Diagnostic.Kind.WARNING);
1073+
}
1074+
1075+
private @Nullable Element getIdMember() {
1076+
for ( Element e : element.getEnclosedElements() ) {
1077+
if ( hasAnnotation( e, ID, EMBEDDED_ID ) ) {
1078+
return e;
1079+
}
10711080
}
1081+
return null;
10721082
}
10731083

10741084
private boolean isPersistent(Element memberOfClass, AccessType membersKind) {

0 commit comments

Comments
 (0)