44 */
55package org .hibernate .bytecode .enhance .internal .bytebuddy ;
66
7- import java .lang .annotation .Annotation ;
8- import java .lang .reflect .Modifier ;
9- import java .util .ArrayList ;
10- import java .util .Collection ;
11- import java .util .Collections ;
12- import java .util .List ;
13- import java .util .Map ;
14- import java .util .Objects ;
15- import java .util .Optional ;
16- import java .util .function .Supplier ;
17-
7+ import jakarta .persistence .Access ;
8+ import jakarta .persistence .AccessType ;
9+ import jakarta .persistence .metamodel .Type ;
10+ import net .bytebuddy .asm .Advice ;
11+ import net .bytebuddy .description .annotation .AnnotationDescription ;
12+ import net .bytebuddy .description .annotation .AnnotationList ;
13+ import net .bytebuddy .description .field .FieldDescription ;
14+ import net .bytebuddy .description .field .FieldDescription .InDefinedShape ;
15+ import net .bytebuddy .description .method .MethodDescription ;
16+ import net .bytebuddy .description .type .TypeDefinition ;
17+ import net .bytebuddy .description .type .TypeDescription ;
18+ import net .bytebuddy .description .type .TypeDescription .Generic ;
19+ import net .bytebuddy .description .type .TypeList ;
20+ import net .bytebuddy .dynamic .DynamicType ;
21+ import net .bytebuddy .dynamic .scaffold .MethodGraph ;
22+ import net .bytebuddy .implementation .FieldAccessor ;
23+ import net .bytebuddy .implementation .FixedValue ;
24+ import net .bytebuddy .implementation .Implementation ;
25+ import net .bytebuddy .implementation .StubMethod ;
1826import org .hibernate .Version ;
1927import org .hibernate .bytecode .enhance .VersionMismatchException ;
2028import org .hibernate .bytecode .enhance .internal .tracker .CompositeOwnerTracker ;
3947import org .hibernate .internal .CoreLogging ;
4048import org .hibernate .internal .CoreMessageLogger ;
4149
42- import jakarta .persistence .Access ;
43- import jakarta .persistence .AccessType ;
44- import jakarta .persistence .metamodel .Type ;
45- import net .bytebuddy .asm .Advice ;
46- import net .bytebuddy .description .annotation .AnnotationDescription ;
47- import net .bytebuddy .description .annotation .AnnotationList ;
48- import net .bytebuddy .description .field .FieldDescription ;
49- import net .bytebuddy .description .field .FieldDescription .InDefinedShape ;
50- import net .bytebuddy .description .method .MethodDescription ;
51- import net .bytebuddy .description .type .TypeDefinition ;
52- import net .bytebuddy .description .type .TypeDescription ;
53- import net .bytebuddy .description .type .TypeDescription .Generic ;
54- import net .bytebuddy .dynamic .DynamicType ;
55- import net .bytebuddy .implementation .FieldAccessor ;
56- import net .bytebuddy .implementation .FixedValue ;
57- import net .bytebuddy .implementation .Implementation ;
58- import net .bytebuddy .implementation .StubMethod ;
50+ import java .lang .annotation .Annotation ;
51+ import java .lang .reflect .Modifier ;
52+ import java .util .ArrayList ;
53+ import java .util .Collection ;
54+ import java .util .Collections ;
55+ import java .util .List ;
56+ import java .util .Map ;
57+ import java .util .Objects ;
58+ import java .util .Optional ;
59+ import java .util .function .Supplier ;
5960
6061import static net .bytebuddy .matcher .ElementMatchers .isDefaultFinalizer ;
6162
@@ -170,6 +171,11 @@ private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builde
170171 }
171172
172173 if ( enhancementContext .isEntityClass ( managedCtClass ) ) {
174+ if ( hasUnsupportedAttributeNaming ( managedCtClass ) ) {
175+ // do not enhance classes with mismatched names for PROPERTY-access persistent attributes
176+ return null ;
177+ }
178+
173179 log .debugf ( "Enhancing [%s] as Entity" , managedCtClass .getName () );
174180 DynamicType .Builder <?> builder = builderSupplier .get ();
175181 builder = builder .implement ( ManagedEntity .class )
@@ -329,6 +335,11 @@ private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builde
329335 return createTransformer ( managedCtClass ).applyTo ( builder );
330336 }
331337 else if ( enhancementContext .isCompositeClass ( managedCtClass ) ) {
338+ if ( hasUnsupportedAttributeNaming ( managedCtClass ) ) {
339+ // do not enhance classes with mismatched names for PROPERTY-access persistent attributes
340+ return null ;
341+ }
342+
332343 log .debugf ( "Enhancing [%s] as Composite" , managedCtClass .getName () );
333344
334345 DynamicType .Builder <?> builder = builderSupplier .get ();
@@ -362,6 +373,12 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
362373 return createTransformer ( managedCtClass ).applyTo ( builder );
363374 }
364375 else if ( enhancementContext .isMappedSuperclassClass ( managedCtClass ) ) {
376+
377+ // Check for HHH-16572 (PROPERTY attributes with mismatched field and method names)
378+ if ( hasUnsupportedAttributeNaming ( managedCtClass ) ) {
379+ return null ;
380+ }
381+
365382 log .debugf ( "Enhancing [%s] as MappedSuperclass" , managedCtClass .getName () );
366383
367384 DynamicType .Builder <?> builder = builderSupplier .get ();
@@ -378,6 +395,82 @@ else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
378395 }
379396 }
380397
398+ /**
399+ * Check whether an entity class ({@code managedCtClass}) has mismatched names between a persistent field and its
400+ * getter/setter when using {@link AccessType#PROPERTY}, which Hibernate does not currently support for enhancement.
401+ * See https://hibernate.atlassian.net/browse/HHH-16572
402+ */
403+ private boolean hasUnsupportedAttributeNaming (TypeDescription managedCtClass ) {
404+ // For process access rules, See https://jakarta.ee/specifications/persistence/3.2/jakarta-persistence-spec-3.2#default-access-type
405+ // and https://jakarta.ee/specifications/persistence/3.2/jakarta-persistence-spec-3.2#a122
406+ //
407+ // This check will determine if entity field names do not match Property accessor method name
408+ // For example:
409+ // @Entity
410+ // class Book {
411+ // Integer id;
412+ // String smtg;
413+ //
414+ // @Id Integer getId() { return id; }
415+ // String getSomething() { return smtg; }
416+ // }
417+ //
418+ // Check name of the getter/setter method with persistence annotation and getter/setter method name that doesn't refer to an entity field
419+ // and will return false. If the property accessor method(s) are named to match the field name(s), return true.
420+ boolean propertyHasAnnotation = false ;
421+ MethodGraph .Linked methodGraph = MethodGraph .Compiler .Default .forJavaHierarchy ().compile ((TypeDefinition ) managedCtClass );
422+ for (MethodGraph .Node node : methodGraph .listNodes ()) {
423+ MethodDescription methodDescription = node .getRepresentative ();
424+ if (methodDescription .getDeclaringType ().represents (Object .class )) { // skip class java.lang.Object methods
425+ continue ;
426+ }
427+
428+ String methodName = methodDescription .getActualName ();
429+ if (methodName .equals ("" ) ||
430+ (!methodName .startsWith ("get" ) && !methodName .startsWith ("set" ) && !methodName .startsWith ("is" ))) {
431+ continue ;
432+ }
433+ String methodFieldName ;
434+ if (methodName .startsWith ("is" )) { // skip past "is"
435+ methodFieldName = methodName .substring (2 );
436+ }
437+ else if (methodName .startsWith ("get" ) ||
438+ methodName .startsWith ("set" )) { // skip past "get" or "set"
439+ methodFieldName = methodName .substring (3 );
440+ }
441+ else {
442+ // not a property accessor method so ignore it
443+ continue ;
444+ }
445+ boolean propertyNameMatchesFieldName = false ;
446+ // convert field letter to lower case
447+ methodFieldName = methodFieldName .substring (0 , 1 ).toLowerCase () + methodFieldName .substring (1 );
448+ TypeList typeList = methodDescription .getDeclaredAnnotations ().asTypeList ();
449+ if (typeList .stream ().anyMatch (typeDefinitions ->
450+ (typeDefinitions .getName ().contains ("jakarta.persistence" )))) {
451+ propertyHasAnnotation = true ;
452+ }
453+ for (FieldDescription ctField : methodDescription .getDeclaringType ().getDeclaredFields ()) {
454+ if (!Modifier .isStatic (ctField .getModifiers ())) {
455+ AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription (enhancementContext , ctField );
456+ boolean containsPropertyAccessorMethods = false ;
457+ if (enhancementContext .isPersistentField (annotatedField )) {
458+ if (methodFieldName .equals (ctField .getActualName ())) {
459+ propertyNameMatchesFieldName = true ;
460+ break ;
461+ }
462+ }
463+ }
464+ }
465+ if (propertyHasAnnotation && !propertyNameMatchesFieldName ) {
466+ log .debugf ("Skipping enhancement of [%s]: due to class [%s] not having a property accessor method name matching field name [%s]" ,
467+ managedCtClass , methodDescription .getDeclaringType ().getActualName (), methodFieldName );
468+ return true ;
469+ }
470+ }
471+ return false ;
472+ }
473+
381474 private static void verifyVersions (TypeDescription managedCtClass , ByteBuddyEnhancementContext enhancementContext ) {
382475 final AnnotationDescription .Loadable <EnhancementInfo > existingInfo = managedCtClass
383476 .getDeclaredAnnotations ()
@@ -493,7 +586,7 @@ private Collection<AnnotatedFieldDescription> collectInheritCollectionFields(Typ
493586 AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription ( enhancementContext , ctField );
494587 if ( enhancementContext .isPersistentField ( annotatedField ) && enhancementContext .isMappedCollection ( annotatedField ) ) {
495588 if ( ctField .getType ().asErasure ().isAssignableTo ( Collection .class ) || ctField .getType ().asErasure ().isAssignableTo ( Map .class ) ) {
496- collectionList .add ( annotatedField );
589+ collectionList .add ( annotatedField );
497590 }
498591 }
499592 }
0 commit comments