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 <gavin@hibernate.org>
This commit is contained in:
parent
356ea205ff
commit
3457b2d283
|
@ -75,6 +75,7 @@ import static org.hibernate.grammars.hql.HqlLexer.HAVING;
|
||||||
import static org.hibernate.grammars.hql.HqlLexer.ORDER;
|
import static org.hibernate.grammars.hql.HqlLexer.ORDER;
|
||||||
import static org.hibernate.grammars.hql.HqlLexer.WHERE;
|
import static org.hibernate.grammars.hql.HqlLexer.WHERE;
|
||||||
import static org.hibernate.internal.util.StringHelper.qualify;
|
import static org.hibernate.internal.util.StringHelper.qualify;
|
||||||
|
import static org.hibernate.internal.util.StringHelper.unqualify;
|
||||||
import static org.hibernate.processor.annotation.AbstractQueryMethod.isSessionParameter;
|
import static org.hibernate.processor.annotation.AbstractQueryMethod.isSessionParameter;
|
||||||
import static org.hibernate.processor.annotation.AbstractQueryMethod.isSpecialParam;
|
import static org.hibernate.processor.annotation.AbstractQueryMethod.isSpecialParam;
|
||||||
import static org.hibernate.processor.annotation.QueryMethod.isOrderParam;
|
import static org.hibernate.processor.annotation.QueryMethod.isOrderParam;
|
||||||
|
@ -966,8 +967,9 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
||||||
final TypeElement assocTypeElement = (TypeElement) assocDeclaredType.asElement();
|
final TypeElement assocTypeElement = (TypeElement) assocDeclaredType.asElement();
|
||||||
if ( hasAnnotation(assocTypeElement, ENTITY) ) {
|
if ( hasAnnotation(assocTypeElement, ENTITY) ) {
|
||||||
final AnnotationValue mappedBy = getAnnotationValue(annotation, "mappedBy");
|
final AnnotationValue mappedBy = getAnnotationValue(annotation, "mappedBy");
|
||||||
final String propertyName = mappedBy == null ? null : mappedBy.getValue().toString();
|
if ( mappedBy != null ) {
|
||||||
validateBidirectionalMapping(memberOfClass, annotation, propertyName, assocTypeElement);
|
validateBidirectionalMapping(memberOfClass, annotation, mappedBy, assocTypeElement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
message(memberOfClass, "type '" + assocTypeElement.getSimpleName()
|
message(memberOfClass, "type '" + assocTypeElement.getSimpleName()
|
||||||
|
@ -983,30 +985,27 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateBidirectionalMapping(
|
private void validateBidirectionalMapping(
|
||||||
Element memberOfClass, AnnotationMirror annotation, @Nullable String mappedBy, TypeElement assocTypeElement) {
|
Element memberOfClass, AnnotationMirror annotation, AnnotationValue annotationVal, TypeElement assocTypeElement) {
|
||||||
if ( mappedBy != null && !mappedBy.isEmpty() ) {
|
final String mappedBy = annotationVal.getValue().toString();
|
||||||
if ( mappedBy.equals("<error>") ) {
|
if ( mappedBy != null && !mappedBy.isEmpty()
|
||||||
return;
|
// this happens for a typesafe ref, e.g. Page_BOOK
|
||||||
// throw new ProcessLaterException();
|
// TODO: we should queue it to validate it later somehow
|
||||||
}
|
&& !mappedBy.equals( "<error>" ) ) {
|
||||||
if ( mappedBy.indexOf('.')>0 ) {
|
if ( mappedBy.indexOf( '.' ) > 0 ) {
|
||||||
//we don't know how to handle paths yet
|
//we don't know how to handle paths yet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final AnnotationValue annotationVal =
|
for ( Element member : context.getAllMembers( assocTypeElement ) ) {
|
||||||
castNonNull(getAnnotationValue(annotation, "mappedBy"));
|
if ( propertyName( this, member ).contentEquals( mappedBy )
|
||||||
for ( Element member : context.getAllMembers(assocTypeElement) ) {
|
&& compatibleAccess( assocTypeElement, member ) ) {
|
||||||
if ( propertyName(this, member).contentEquals(mappedBy)
|
validateBackRef( memberOfClass, annotation, assocTypeElement, member, annotationVal );
|
||||||
&& compatibleAccess(assocTypeElement, member) ) {
|
|
||||||
validateBackRef(memberOfClass, annotation, assocTypeElement, member, annotationVal);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// not found
|
// not found
|
||||||
message(memberOfClass, annotation,
|
message( memberOfClass, annotation, annotationVal,
|
||||||
annotationVal,
|
|
||||||
"no matching member in '" + assocTypeElement.getSimpleName() + "'",
|
"no matching member in '" + assocTypeElement.getSimpleName() + "'",
|
||||||
Diagnostic.Kind.ERROR);
|
Diagnostic.Kind.ERROR );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1024,53 +1023,64 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
||||||
Element memberOfClass,
|
Element memberOfClass,
|
||||||
AnnotationMirror annotation,
|
AnnotationMirror annotation,
|
||||||
TypeElement assocTypeElement,
|
TypeElement assocTypeElement,
|
||||||
Element member,
|
Element referencedMember,
|
||||||
AnnotationValue annotationVal) {
|
AnnotationValue annotationVal) {
|
||||||
final TypeMirror backType;
|
final TypeMirror backType;
|
||||||
|
final String expectedMappingAnnotation;
|
||||||
switch ( annotation.getAnnotationType().asElement().toString() ) {
|
switch ( annotation.getAnnotationType().asElement().toString() ) {
|
||||||
case ONE_TO_ONE:
|
case ONE_TO_ONE:
|
||||||
backType = attributeType(member);
|
backType = attributeType(referencedMember);
|
||||||
if ( !hasAnnotation(member, ONE_TO_ONE) ) {
|
expectedMappingAnnotation = ONE_TO_ONE;
|
||||||
message(memberOfClass, annotation, annotationVal,
|
|
||||||
"member '" + member.getSimpleName()
|
|
||||||
+ "' of '" + assocTypeElement.getSimpleName()
|
|
||||||
+ "' is not annotated '@OneToOne'",
|
|
||||||
Diagnostic.Kind.WARNING);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case ONE_TO_MANY:
|
case ONE_TO_MANY:
|
||||||
backType = attributeType(member);
|
backType = attributeType(referencedMember);
|
||||||
if ( !hasAnnotation(member, MANY_TO_ONE) ) {
|
expectedMappingAnnotation = MANY_TO_ONE;
|
||||||
message(memberOfClass, annotation, annotationVal,
|
|
||||||
"member '" + member.getSimpleName()
|
|
||||||
+ "' of '" + assocTypeElement.getSimpleName()
|
|
||||||
+ "' is not annotated '@ManyToOne'",
|
|
||||||
Diagnostic.Kind.WARNING);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case MANY_TO_MANY:
|
case MANY_TO_MANY:
|
||||||
backType = elementType( attributeType(member) );
|
backType = elementType( attributeType(referencedMember) );
|
||||||
if ( !hasAnnotation(member, MANY_TO_MANY) ) {
|
expectedMappingAnnotation = MANY_TO_MANY;
|
||||||
message(memberOfClass, annotation, annotationVal,
|
|
||||||
"member '" + member.getSimpleName()
|
|
||||||
+ "' of '" + assocTypeElement.getSimpleName()
|
|
||||||
+ "' is not annotated '@ManyToMany'",
|
|
||||||
Diagnostic.Kind.WARNING);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new AssertionFailure("should not have a mappedBy");
|
throw new AssertionFailure("should not have a mappedBy");
|
||||||
}
|
}
|
||||||
if ( backType!=null
|
if ( backType != null ) {
|
||||||
&& !context.getTypeUtils().isSameType(backType, element.asType()) ) {
|
final Element idMember = getIdMember();
|
||||||
message(memberOfClass, annotation, annotationVal,
|
final Types typeUtils = context.getTypeUtils();
|
||||||
"member '" + member.getSimpleName()
|
if ( idMember != null && typeUtils.isSameType( backType, idMember.asType() ) ) {
|
||||||
+ "' of '" + assocTypeElement.getSimpleName()
|
// mappedBy references a regular field of the same type as the entity id
|
||||||
+ "' is not of type '" + element.getSimpleName() + "'",
|
//TODO: any other validation to do here??
|
||||||
Diagnostic.Kind.WARNING);
|
}
|
||||||
|
else if ( typeUtils.isSameType( backType, element.asType() ) ) {
|
||||||
|
// mappedBy references a field of the same type as the entity
|
||||||
|
// it needs to be mapped as the appropriate sort of association
|
||||||
|
if ( !hasAnnotation( referencedMember, expectedMappingAnnotation ) ) {
|
||||||
|
message(memberOfClass, annotation, annotationVal,
|
||||||
|
"member '" + referencedMember.getSimpleName()
|
||||||
|
+ "' of '" + assocTypeElement.getSimpleName()
|
||||||
|
+ "' is not annotated '@" + unqualify(expectedMappingAnnotation) + "'",
|
||||||
|
Diagnostic.Kind.WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// mappedBy references a field which seems to be of the wrong type
|
||||||
|
message( memberOfClass, annotation, annotationVal,
|
||||||
|
"member '" + referencedMember.getSimpleName()
|
||||||
|
+ "' of '" + assocTypeElement.getSimpleName()
|
||||||
|
+ "' is not of type '" + element.getSimpleName() + "'",
|
||||||
|
Diagnostic.Kind.WARNING );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @Nullable Element getIdMember() {
|
||||||
|
for ( Element e : element.getEnclosedElements() ) {
|
||||||
|
if ( hasAnnotation( e, ID, EMBEDDED_ID ) ) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isPersistent(Element memberOfClass, AccessType membersKind) {
|
private boolean isPersistent(Element memberOfClass, AccessType membersKind) {
|
||||||
return ( entityAccessTypeInfo.getAccessType() == membersKind
|
return ( entityAccessTypeInfo.getAccessType() == membersKind
|
||||||
|| determineAnnotationSpecifiedAccessType( memberOfClass ) != null )
|
|| determineAnnotationSpecifiedAccessType( memberOfClass ) != null )
|
||||||
|
|
Loading…
Reference in New Issue