1111import java .util .ArrayList ;
1212import java .util .Collection ;
1313import java .util .Collections ;
14+ import java .util .HashSet ;
1415import java .util .List ;
1516import java .util .Map ;
1617import java .util .Objects ;
1718import java .util .Optional ;
19+ import java .util .Set ;
1820import java .util .function .Supplier ;
1921
22+ import net .bytebuddy .description .type .TypeList ;
23+ import net .bytebuddy .dynamic .scaffold .MethodGraph ;
2024import org .hibernate .Version ;
2125import org .hibernate .bytecode .enhance .VersionMismatchException ;
2226import org .hibernate .bytecode .enhance .internal .tracker .CompositeOwnerTracker ;
@@ -172,6 +176,12 @@ private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builde
172176 }
173177
174178 if ( enhancementContext .isEntityClass ( managedCtClass ) ) {
179+
180+ // Check for HHH-16572 (PROPERTY attributes with mismatched field and method names)
181+ if ( !allowedEnhancementCheck ( managedCtClass ) ) {
182+ return null ;
183+ }
184+
175185 log .debugf ( "Enhancing [%s] as Entity" , managedCtClass .getName () );
176186 DynamicType .Builder <?> builder = builderSupplier .get ();
177187 builder = builder .implement ( ManagedEntity .class )
@@ -331,6 +341,12 @@ private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builde
331341 return createTransformer ( managedCtClass ).applyTo ( builder );
332342 }
333343 else if ( enhancementContext .isCompositeClass ( managedCtClass ) ) {
344+
345+ // Check for HHH-16572 (PROPERTY attributes with mismatched field and method names)
346+ if ( !allowedEnhancementCheck ( managedCtClass ) ) {
347+ return null ;
348+ }
349+
334350 log .debugf ( "Enhancing [%s] as Composite" , managedCtClass .getName () );
335351
336352 DynamicType .Builder <?> builder = builderSupplier .get ();
@@ -364,6 +380,12 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
364380 return createTransformer ( managedCtClass ).applyTo ( builder );
365381 }
366382 else if ( enhancementContext .isMappedSuperclassClass ( managedCtClass ) ) {
383+
384+ // Check for HHH-16572 (PROPERTY attributes with mismatched field and method names)
385+ if ( !allowedEnhancementCheck ( managedCtClass ) ) {
386+ return null ;
387+ }
388+
367389 log .debugf ( "Enhancing [%s] as MappedSuperclass" , managedCtClass .getName () );
368390
369391 DynamicType .Builder <?> builder = builderSupplier .get ();
@@ -380,6 +402,81 @@ else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
380402 }
381403 }
382404
405+ // See HHH-16572
406+ // return true if enhancement is supported
407+ private boolean allowedEnhancementCheck (TypeDescription managedCtClass ) {
408+ // For process access rules, See https://jakarta.ee/specifications/persistence/3.2/jakarta-persistence-spec-3.2#default-access-type
409+ // and https://jakarta.ee/specifications/persistence/3.2/jakarta-persistence-spec-3.2#a122
410+ //
411+ // This check will determine if entity field names do not match Property accessor method name
412+ // For example:
413+ // @Entity
414+ // class Book {
415+ // Integer id;
416+ // String smtg;
417+ //
418+ // @Id Integer getId() { return id; }
419+ // String getSomething() { return smtg; }
420+ // }
421+ //
422+ // Check name of the getter/setter method with Peristence annotation and getter/setter method name that doesn't refer to an entity field
423+ // and will return false. If the property accessor method(s) are named to match the field name(s), return true.
424+ boolean propertyHasAnnotation = false ;
425+ MethodGraph .Linked methodGraph = MethodGraph .Compiler .Default .forJavaHierarchy ().compile ((TypeDefinition )managedCtClass );
426+ for (MethodGraph .Node node : methodGraph .listNodes ()) {
427+ MethodDescription methodDescription = node .getRepresentative ();
428+ if (methodDescription .getDeclaringType ().represents (Object .class )) { // skip class java.lang.Object methods
429+ continue ;
430+ }
431+
432+ String methodName = methodDescription .getActualName ();
433+ if (methodName .equals ("" ) ||
434+ (!methodName .startsWith ("get" ) && !methodName .startsWith ("set" ) && !methodName .startsWith ("is" ))) {
435+ continue ;
436+ }
437+ String methodFieldName ;
438+ if (methodName .startsWith ("is" )) { // skip past "is"
439+ methodFieldName = methodName .substring (2 );
440+ } else if (methodName .startsWith ("get" ) || methodName .startsWith ("set" )){ // skip past "get" or "set"
441+ methodFieldName = methodName .substring (3 );
442+ } else {
443+ // not a property accessor method so ignore it
444+ continue ;
445+ }
446+ boolean propertyNameMatchesFieldName =false ;
447+ // convert field letter to lower case
448+ methodFieldName = methodFieldName .substring (0 , 1 ).toLowerCase () + methodFieldName .substring (1 );
449+ TypeList typeList = methodDescription .getDeclaredAnnotations ().asTypeList ();
450+ if ( typeList .stream ().anyMatch (typeDefinitions ->
451+ (typeDefinitions .getName ().contains ("jakarta.persistence.Id" ) ||
452+ typeDefinitions .getName ().contains ("jakarta.persistence.Inheritance" ) ||
453+ typeDefinitions .getName ().contains ("jakarta.persistence.ManyToOne" ) ||
454+ typeDefinitions .getName ().contains ("jakarta.persistence.ManyToMany" ) ||
455+ typeDefinitions .getName ().contains ("jakarta.persistence.OneToMany" ) ||
456+ typeDefinitions .getName ().contains ("jakarta.persistence.OneToOne" )))) {
457+ propertyHasAnnotation = true ;
458+ }
459+ for (FieldDescription ctField : methodDescription .getDeclaringType ().getDeclaredFields ()) {
460+ if (!Modifier .isStatic (ctField .getModifiers ())) {
461+ AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription (enhancementContext , ctField );
462+ boolean containsPropertyAccessorMethods = false ;
463+ if (enhancementContext .isPersistentField (annotatedField )) {
464+ if (methodFieldName .equals (ctField .getActualName ())) {
465+ propertyNameMatchesFieldName = true ;
466+ break ;
467+ }
468+ }
469+ }
470+ }
471+ if ( propertyHasAnnotation && !propertyNameMatchesFieldName ) {
472+ log .debugf ("Skipping enhancement of [%s]: due to class [%s] not having a property accessor method name matching field name [%s]" ,
473+ managedCtClass , methodDescription .getDeclaringType ().getActualName (), methodFieldName );
474+ return false ;
475+ }
476+ }
477+ return true ;
478+ }
479+
383480 private static void verifyVersions (TypeDescription managedCtClass , ByteBuddyEnhancementContext enhancementContext ) {
384481 final AnnotationDescription .Loadable <EnhancementInfo > existingInfo = managedCtClass
385482 .getDeclaredAnnotations ()
@@ -495,7 +592,7 @@ private Collection<AnnotatedFieldDescription> collectInheritCollectionFields(Typ
495592 AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription ( enhancementContext , ctField );
496593 if ( enhancementContext .isPersistentField ( annotatedField ) && enhancementContext .isMappedCollection ( annotatedField ) ) {
497594 if ( ctField .getType ().asErasure ().isAssignableTo ( Collection .class ) || ctField .getType ().asErasure ().isAssignableTo ( Map .class ) ) {
498- collectionList .add ( annotatedField );
595+ collectionList .add ( annotatedField );
499596 }
500597 }
501598 }
0 commit comments