Skip to content

Commit 2695100

Browse files
committed
HHH-19303 validate @id fields against @IdClass in Processor
1 parent a0be804 commit 2695100

File tree

3 files changed

+74
-16
lines changed

3 files changed

+74
-16
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ private void handleNamedQueryAnnotation(String annotationName, boolean checkHql)
7272
private void handleNamedQueryRepeatableAnnotation(String annotationName, boolean checkHql) {
7373
final AnnotationMirror mirror = getAnnotationMirror( getElement(), annotationName );
7474
if ( mirror != null ) {
75-
final AnnotationValue value = getAnnotationValue( mirror, "value" );
75+
final AnnotationValue value = getAnnotationValue( mirror );
7676
if ( value != null ) {
7777
@SuppressWarnings("unchecked")
7878
final List<? extends AnnotationValue> annotationValues =
@@ -151,7 +151,7 @@ && isJavaIdentifierStart( name.charAt(1) )
151151
private void addAuxiliaryMembersForRepeatableAnnotation(String annotationName, String prefix) {
152152
final AnnotationMirror mirror = getAnnotationMirror( getElement(), annotationName );
153153
if ( mirror != null ) {
154-
final AnnotationValue value = getAnnotationValue( mirror, "value" );
154+
final AnnotationValue value = getAnnotationValue( mirror );
155155
if ( value != null ) {
156156
@SuppressWarnings("unchecked")
157157
final List<? extends AnnotationValue> annotationValues =

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

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -463,31 +463,85 @@ && containsAnnotation( method, HQL, SQL, FIND ) ) {
463463
initialized = true;
464464
}
465465

466-
private void addIdClassIfNeeded(List<? extends Element> fields, List<? extends Element> methods) {
466+
/**
467+
* Creates a generated id class named {@code Entity_.Id} if the
468+
* entity has multiple {@code @Id} fields, but no {@code @IdClass}
469+
* annotation.
470+
*/
471+
private void addIdClassIfNeeded(List<VariableElement> fields, List<ExecutableElement> methods) {
467472
if ( hasAnnotation( element, ID_CLASS ) ) {
468-
return;
473+
checkIdMembers( fields, methods );
474+
}
475+
else {
476+
final List<MetaAttribute> components = getIdMemberNames( fields, methods );
477+
if ( components.size() >= 2 ) {
478+
putMember( ID_CLASS_MEMBER_NAME, new IdClassMetaAttribute( this, components ) );
479+
}
469480
}
481+
}
482+
483+
private List<MetaAttribute> getIdMemberNames(List<VariableElement> fields, List<ExecutableElement> methods) {
470484
final List<MetaAttribute> components = new ArrayList<>();
471-
for ( Element field : fields ) {
485+
for ( var field : fields ) {
472486
if ( hasAnnotation( field, ID ) && isPersistent( field, AccessType.FIELD ) ) {
473487
final String propertyName = propertyName( field );
474488
if ( members.containsKey( propertyName ) ) {
475489
components.add( members.get( propertyName ) );
476490
}
477491
}
478492
}
479-
for ( Element method : methods ) {
493+
for ( var method : methods ) {
480494
if ( hasAnnotation( method, ID ) && isPersistent( method, AccessType.PROPERTY ) ) {
481495
final String propertyName = propertyName( method );
482496
if ( members.containsKey( propertyName ) ) {
483497
components.add( members.get( propertyName ) );
484498
}
485499
}
486500
}
487-
if ( components.size() < 2 ) {
488-
return;
501+
return components;
502+
}
503+
504+
private void checkIdMembers(List<VariableElement> fields, List<ExecutableElement> methods) {
505+
final AnnotationMirror annotationMirror = getAnnotationMirror( element, ID_CLASS );
506+
if ( annotationMirror != null ) {
507+
final AnnotationValue annotationValue = getAnnotationValue( annotationMirror );
508+
if ( annotationValue != null && annotationValue.getValue() instanceof DeclaredType declaredType ) {
509+
final TypeElement idClass = (TypeElement) declaredType.asElement();
510+
for ( var field : fields ) {
511+
if ( hasAnnotation( field, ID ) && isPersistent( field, AccessType.FIELD ) ) {
512+
idClass.getEnclosedElements().stream()
513+
.filter( element -> element.getKind() == ElementKind.FIELD
514+
&& element.getSimpleName().contentEquals( field.getSimpleName() ) )
515+
.findAny()
516+
.ifPresentOrElse( match -> {
517+
if ( !context.getTypeUtils().isSameType( field.asType(), match.asType() ) ) {
518+
context.message( match,
519+
"id field should be of type '" + field.asType() + "'",
520+
Diagnostic.Kind.ERROR );
521+
}
522+
},
523+
() -> context.message( field,
524+
"no matching field in id class '" + idClass.getSimpleName() + "'",
525+
Diagnostic.Kind.ERROR ) );
526+
}
527+
}
528+
for ( var method : methods ) {
529+
if ( hasAnnotation( method, ID ) && isPersistent( method, AccessType.PROPERTY ) ) {
530+
idClass.getEnclosedElements().stream()
531+
.filter( element -> element.getKind() == ElementKind.METHOD
532+
&& element.getSimpleName().contentEquals( method.getSimpleName() )
533+
&& context.getTypeUtils()
534+
.isSameType( method.asType(), element.asType() ) )
535+
.findAny()
536+
.ifPresentOrElse( match -> {
537+
},
538+
() -> context.message( method,
539+
"no matching property in id class '" + idClass.getSimpleName() + "'",
540+
Diagnostic.Kind.ERROR ) );
541+
}
542+
}
543+
}
489544
}
490-
putMember( ID_CLASS_MEMBER_NAME, new IdClassMetaAttribute( this, components ) );
491545
}
492546

493547
private boolean checkEntities(List<ExecutableElement> lifecycleMethods) {
@@ -1767,7 +1821,7 @@ private List<OrderBy> orderByList(ExecutableElement method, TypeElement returnTy
17671821
final List<OrderBy> result = new ArrayList<>();
17681822
@SuppressWarnings("unchecked")
17691823
final List<AnnotationValue> list = (List<AnnotationValue>)
1770-
castNonNull( getAnnotationValue( orderByList, "value" ) ).getValue();
1824+
castNonNull( getAnnotationValue( orderByList ) ).getValue();
17711825
for ( AnnotationValue element : list ) {
17721826
result.add( orderByExpression( castNonNull( (AnnotationMirror) element.getValue() ), entityType, method ) );
17731827
}
@@ -1782,7 +1836,7 @@ private List<OrderBy> orderByList(ExecutableElement method, TypeElement returnTy
17821836
}
17831837

17841838
private OrderBy orderByExpression(AnnotationMirror orderBy, TypeElement entityType, ExecutableElement method) {
1785-
final String fieldName = castNonNull( getAnnotationValue(orderBy, "value") ).getValue().toString();
1839+
final String fieldName = castNonNull( getAnnotationValue(orderBy) ).getValue().toString();
17861840
if ( fieldName.equals("<error>") ) {
17871841
throw new ProcessLaterException();
17881842
}
@@ -2138,7 +2192,7 @@ else if ( containsAnnotation( member, NATURAL_ID ) ) {
21382192
else {
21392193
final AnnotationMirror idClass = getAnnotationMirror( entityType, ID_CLASS );
21402194
if ( idClass != null ) {
2141-
final AnnotationValue value = getAnnotationValue( idClass, "value" );
2195+
final AnnotationValue value = getAnnotationValue( idClass );
21422196
if ( value != null ) {
21432197
if ( context.getTypeUtils().isSameType( param.asType(), (TypeMirror) value.getValue() ) ) {
21442198
return FieldType.ID;
@@ -2377,7 +2431,7 @@ private void addQueryMethod(
23772431
containerTypeName = containerType.getQualifiedName().toString();
23782432
}
23792433

2380-
final AnnotationValue value = getAnnotationValue( mirror, "value" );
2434+
final AnnotationValue value = getAnnotationValue( mirror );
23812435
if ( value != null && value.getValue() instanceof String queryString ) {
23822436
addQueryMethod( method, returnType, containerTypeName, mirror, isNative, value, queryString );
23832437
}
@@ -3026,7 +3080,7 @@ private static String parameterName(VariableElement parameter) {
30263080
final AnnotationMirror param = getAnnotationMirror( parameter, "jakarta.data.repository.Param" );
30273081
if ( by != null ) {
30283082
final String name =
3029-
castNonNull(getAnnotationValue(by, "value"))
3083+
castNonNull(getAnnotationValue(by))
30303084
.getValue().toString();
30313085
if ( name.contains("<error>") ) {
30323086
throw new ProcessLaterException();
@@ -3037,7 +3091,7 @@ private static String parameterName(VariableElement parameter) {
30373091
}
30383092
else if ( param != null ) {
30393093
final String name =
3040-
castNonNull(getAnnotationValue(param, "value"))
3094+
castNonNull(getAnnotationValue(param))
30413095
.getValue().toString();
30423096
if ( name.contains("<error>") ) {
30433097
throw new ProcessLaterException();

tooling/metamodel-generator/src/main/java/org/hibernate/processor/util/TypeUtils.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,10 @@ public static boolean hasAnnotation(Element element, String... qualifiedNames) {
260260
return false;
261261
}
262262

263+
public static @Nullable AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror) {
264+
return getAnnotationValue( annotationMirror, DEFAULT_ANNOTATION_PARAMETER_NAME );
265+
}
266+
263267
public static @Nullable AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String member) {
264268
assert annotationMirror != null;
265269
assert member != null;
@@ -474,7 +478,7 @@ private static boolean isIdAnnotation(AnnotationMirror annotationMirror) {
474478
public static @Nullable AccessType determineAnnotationSpecifiedAccessType(Element element) {
475479
final AnnotationMirror mirror = getAnnotationMirror( element, ACCESS );
476480
if ( mirror != null ) {
477-
final AnnotationValue accessType = getAnnotationValue( mirror, DEFAULT_ANNOTATION_PARAMETER_NAME );
481+
final AnnotationValue accessType = getAnnotationValue( mirror );
478482
if ( accessType != null ) {
479483
final VariableElement enumValue = (VariableElement) accessType.getValue();
480484
final Name enumValueName = enumValue.getSimpleName();

0 commit comments

Comments
 (0)