30
30
import net .bytebuddy .implementation .FixedValue ;
31
31
import net .bytebuddy .implementation .Implementation ;
32
32
import net .bytebuddy .implementation .StubMethod ;
33
-
34
33
import org .checkerframework .checker .nullness .qual .Nullable ;
35
34
import org .hibernate .AssertionFailure ;
36
35
import org .hibernate .Version ;
76
75
import static net .bytebuddy .matcher .ElementMatchers .isStatic ;
77
76
import static net .bytebuddy .matcher .ElementMatchers .named ;
78
77
import static net .bytebuddy .matcher .ElementMatchers .not ;
78
+ import static org .hibernate .bytecode .enhance .internal .bytebuddy .FeatureMismatchException .Feature .ASSOCIATION_MANAGEMENT ;
79
+ import static org .hibernate .bytecode .enhance .internal .bytebuddy .FeatureMismatchException .Feature .DIRTY_CHECK ;
79
80
80
81
public class EnhancerImpl implements Enhancer {
81
-
82
82
private static final CoreMessageLogger log = CoreLogging .messageLogger ( Enhancer .class );
83
83
84
84
protected final ByteBuddyEnhancementContext enhancementContext ;
85
85
private final ByteBuddyState byteBuddyState ;
86
86
private final EnhancerClassLocator typePool ;
87
87
private final EnhancerImplConstants constants ;
88
+ private final List <? extends Annotation > infoAnnotationList ;
88
89
89
90
/**
90
91
* Constructs the Enhancer, using the given context.
@@ -109,8 +110,11 @@ public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddy
109
110
this .byteBuddyState = Objects .requireNonNull ( byteBuddyState );
110
111
this .typePool = Objects .requireNonNull ( classLocator );
111
112
this .constants = byteBuddyState .getEnhancerConstants ();
113
+
114
+ this .infoAnnotationList = List .of ( createInfoAnnotation ( enhancementContext ) );
112
115
}
113
116
117
+
114
118
/**
115
119
* Performs the enhancement.
116
120
*
@@ -133,7 +137,7 @@ public byte[] enhance(String className, byte[] originalBytes) throws Enhancement
133
137
return byteBuddyState .rewrite ( typePool , safeClassName , byteBuddy -> doEnhance (
134
138
() -> byteBuddy .ignore ( isDefaultFinalizer () )
135
139
.redefine ( typeDescription , typePool .asClassFileLocator () )
136
- .annotateType ( constants . HIBERNATE_VERSION_ANNOTATION ),
140
+ .annotateType ( infoAnnotationList ),
137
141
typeDescription
138
142
) );
139
143
}
@@ -167,14 +171,17 @@ public void discoverTypes(String className, byte[] originalBytes) {
167
171
}
168
172
169
173
private DynamicType .Builder <?> doEnhance (Supplier <DynamicType .Builder <?>> builderSupplier , TypeDescription managedCtClass ) {
170
- // skip if the class was already enhanced. This is very common in WildFly as classloading is highly concurrent.
171
- // We need to ensure that no class is instrumented multiple times as that might result in incorrect bytecode.
172
- // N.B. there is a second check below using a different approach: checking for the marker interfaces,
173
- // which does not address the case of extended bytecode enhancement
174
- // (because it enhances classes that do not end up with these marker interfaces).
175
- // I'm currently inclined to keep both checks, as one is safer and the other has better backwards compatibility.
176
- if ( managedCtClass .getDeclaredAnnotations ().isAnnotationPresent ( EnhancementInfo .class ) ) {
177
- verifyVersions ( managedCtClass , enhancementContext );
174
+ if ( alreadyEnhanced ( managedCtClass ) ) {
175
+ // the class already implements `Managed`. there are 2 broad cases -
176
+ // 1. the user manually implemented `Managed`
177
+ // 2. the class was previously enhanced
178
+ // in either case, look for `@EnhancementInfo` and, if found, verify we can "re-enhance" the class
179
+ final AnnotationDescription .Loadable <EnhancementInfo > infoAnnotation = managedCtClass .getDeclaredAnnotations ().ofType ( EnhancementInfo .class );
180
+ if ( infoAnnotation != null ) {
181
+ // throws an exception if there is a mismatch...
182
+ verifyReEnhancement ( managedCtClass , infoAnnotation .load (), enhancementContext );
183
+ }
184
+ // verification succeeded (or not done) - we can simply skip the enhancement
178
185
log .tracef ( "Skipping enhancement of [%s]: it's already annotated with @EnhancementInfo" , managedCtClass .getName () );
179
186
return null ;
180
187
}
@@ -191,14 +198,6 @@ private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builde
191
198
return null ;
192
199
}
193
200
194
- // handle already enhanced classes
195
- if ( alreadyEnhanced ( managedCtClass ) ) {
196
- verifyVersions ( managedCtClass , enhancementContext );
197
-
198
- log .tracef ( "Skipping enhancement of [%s]: it's already implementing 'Managed'" , managedCtClass .getName () );
199
- return null ;
200
- }
201
-
202
201
if ( enhancementContext .isEntityClass ( managedCtClass ) ) {
203
202
if ( checkUnsupportedAttributeNaming ( managedCtClass , enhancementContext ) ) {
204
203
// do not enhance classes with mismatched names for PROPERTY-access persistent attributes
@@ -258,7 +257,7 @@ private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builde
258
257
259
258
builder = addInterceptorHandling ( builder , managedCtClass );
260
259
261
- if ( enhancementContext .doDirtyCheckingInline ( managedCtClass ) ) {
260
+ if ( enhancementContext .doDirtyCheckingInline () ) {
262
261
List <AnnotatedFieldDescription > collectionFields = collectCollectionFields ( managedCtClass );
263
262
264
263
if ( collectionFields .isEmpty () ) {
@@ -390,7 +389,7 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
390
389
builder = builder .implement ( ManagedComposite .class );
391
390
builder = addInterceptorHandling ( builder , managedCtClass );
392
391
393
- if ( enhancementContext .doDirtyCheckingInline ( managedCtClass ) ) {
392
+ if ( enhancementContext .doDirtyCheckingInline () ) {
394
393
builder = builder .implement ( CompositeTracker .class )
395
394
.defineField (
396
395
EnhancerConstants .TRACKER_COMPOSITE_FIELD_NAME ,
@@ -428,7 +427,7 @@ else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
428
427
builder = builder .implement ( ManagedMappedSuperclass .class );
429
428
return createTransformer ( managedCtClass ).applyTo ( builder );
430
429
}
431
- else if ( enhancementContext .doExtendedEnhancement ( managedCtClass ) ) {
430
+ else if ( enhancementContext .doExtendedEnhancement () ) {
432
431
log .tracef ( "Extended enhancement of [%s]" , managedCtClass .getName () );
433
432
return createTransformer ( managedCtClass ).applyExtended ( builderSupplier .get () );
434
433
}
@@ -438,6 +437,36 @@ else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
438
437
}
439
438
}
440
439
440
+ private void verifyReEnhancement (
441
+ TypeDescription managedCtClass ,
442
+ EnhancementInfo existingInfo ,
443
+ ByteBuddyEnhancementContext enhancementContext ) {
444
+ // first, make sure versions match
445
+ final String enhancementVersion = existingInfo .version ();
446
+ if ( "ignore" .equals ( enhancementVersion ) ) {
447
+ // for testing
448
+ log .debugf ( "Skipping re-enhancement version check for %s due to `ignore`" , managedCtClass .getName () );
449
+ }
450
+ else if ( !Version .getVersionString ().equals ( enhancementVersion ) ) {
451
+ throw new VersionMismatchException ( managedCtClass , enhancementVersion , Version .getVersionString () );
452
+ }
453
+
454
+ FeatureMismatchException .checkFeatureEnablement (
455
+ managedCtClass ,
456
+ DIRTY_CHECK ,
457
+ enhancementContext .doDirtyCheckingInline (),
458
+ existingInfo .includesDirtyChecking ()
459
+ );
460
+
461
+ FeatureMismatchException .checkFeatureEnablement (
462
+ managedCtClass ,
463
+ ASSOCIATION_MANAGEMENT ,
464
+ enhancementContext .doBiDirectionalAssociationManagement (),
465
+ existingInfo .includesAssociationManagement ()
466
+ );
467
+ }
468
+
469
+
441
470
/**
442
471
* Utility that determines the access-type of a mapped class based on an explicit annotation
443
472
* or guessing it from the placement of its identifier property. Implementation should be
@@ -629,31 +658,6 @@ private static boolean checkUnsupportedAttributeNaming(TypeDescription managedCt
629
658
return new String ( chars );
630
659
}
631
660
632
- private static void verifyVersions (TypeDescription managedCtClass , ByteBuddyEnhancementContext enhancementContext ) {
633
- final AnnotationDescription .Loadable <EnhancementInfo > existingInfo = managedCtClass
634
- .getDeclaredAnnotations ()
635
- .ofType ( EnhancementInfo .class );
636
- if ( existingInfo == null ) {
637
- // There is an edge case here where a user manually adds `implement Managed` to
638
- // their domain class, in which case there will most likely not be a
639
- // `EnhancementInfo` annotation. Such cases should simply not do version checking.
640
- //
641
- // However, there is also ambiguity in this case with classes that were enhanced
642
- // with old versions of Hibernate which did not add that annotation as part of
643
- // enhancement. But overall we consider this condition to be acceptable
644
- return ;
645
- }
646
-
647
- final String enhancementVersion = extractVersion ( existingInfo );
648
- if ( !Version .getVersionString ().equals ( enhancementVersion ) ) {
649
- throw new VersionMismatchException ( managedCtClass , enhancementVersion , Version .getVersionString () );
650
- }
651
- }
652
-
653
- private static String extractVersion (AnnotationDescription .Loadable <EnhancementInfo > annotation ) {
654
- return annotation .load ().version ();
655
- }
656
-
657
661
private PersistentAttributeTransformer createTransformer (TypeDescription typeDescription ) {
658
662
return PersistentAttributeTransformer .collectPersistentFields ( typeDescription , enhancementContext , typePool );
659
663
}
@@ -872,4 +876,42 @@ else if ( access != null && access.load().value() == AccessType.FIELD ) {
872
876
}
873
877
}
874
878
879
+
880
+ private static EnhancementInfo createInfoAnnotation (EnhancementContext enhancementContext ) {
881
+ return new EnhancementInfoImpl ( enhancementContext .doDirtyCheckingInline (), enhancementContext .doBiDirectionalAssociationManagement () );
882
+ }
883
+
884
+ private static class EnhancementInfoImpl implements EnhancementInfo {
885
+ private final String version ;
886
+ private final boolean includesDirtyChecking ;
887
+ private final boolean includesAssociationManagement ;
888
+
889
+ public EnhancementInfoImpl (boolean includesDirtyChecking , boolean includesAssociationManagement ) {
890
+ this .version = Version .getVersionString ();
891
+ this .includesDirtyChecking = includesDirtyChecking ;
892
+ this .includesAssociationManagement = includesAssociationManagement ;
893
+ }
894
+
895
+ @ Override
896
+ public String version () {
897
+ return version ;
898
+ }
899
+
900
+ @ Override
901
+ public boolean includesDirtyChecking () {
902
+ return includesDirtyChecking ;
903
+ }
904
+
905
+ @ Override
906
+ public boolean includesAssociationManagement () {
907
+ return includesAssociationManagement ;
908
+ }
909
+
910
+ @ Override
911
+ public Class <? extends Annotation > annotationType () {
912
+ return EnhancementInfo .class ;
913
+ }
914
+ }
915
+
916
+
875
917
}
0 commit comments