3030import net .bytebuddy .implementation .FixedValue ;
3131import net .bytebuddy .implementation .Implementation ;
3232import net .bytebuddy .implementation .StubMethod ;
33-
3433import org .checkerframework .checker .nullness .qual .Nullable ;
3534import org .hibernate .AssertionFailure ;
3635import org .hibernate .Version ;
7675import static net .bytebuddy .matcher .ElementMatchers .isStatic ;
7776import static net .bytebuddy .matcher .ElementMatchers .named ;
7877import 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 ;
7980
8081public class EnhancerImpl implements Enhancer {
81-
8282 private static final CoreMessageLogger log = CoreLogging .messageLogger ( Enhancer .class );
8383
8484 protected final ByteBuddyEnhancementContext enhancementContext ;
8585 private final ByteBuddyState byteBuddyState ;
8686 private final EnhancerClassLocator typePool ;
8787 private final EnhancerImplConstants constants ;
88+ private final List <? extends Annotation > infoAnnotationList ;
8889
8990 /**
9091 * Constructs the Enhancer, using the given context.
@@ -109,8 +110,11 @@ public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddy
109110 this .byteBuddyState = Objects .requireNonNull ( byteBuddyState );
110111 this .typePool = Objects .requireNonNull ( classLocator );
111112 this .constants = byteBuddyState .getEnhancerConstants ();
113+
114+ this .infoAnnotationList = List .of ( createInfoAnnotation ( enhancementContext ) );
112115 }
113116
117+
114118 /**
115119 * Performs the enhancement.
116120 *
@@ -133,7 +137,7 @@ public byte[] enhance(String className, byte[] originalBytes) throws Enhancement
133137 return byteBuddyState .rewrite ( typePool , safeClassName , byteBuddy -> doEnhance (
134138 () -> byteBuddy .ignore ( isDefaultFinalizer () )
135139 .redefine ( typeDescription , typePool .asClassFileLocator () )
136- .annotateType ( constants . HIBERNATE_VERSION_ANNOTATION ),
140+ .annotateType ( infoAnnotationList ),
137141 typeDescription
138142 ) );
139143 }
@@ -167,14 +171,17 @@ public void discoverTypes(String className, byte[] originalBytes) {
167171 }
168172
169173 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
178185 log .tracef ( "Skipping enhancement of [%s]: it's already annotated with @EnhancementInfo" , managedCtClass .getName () );
179186 return null ;
180187 }
@@ -191,14 +198,6 @@ private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builde
191198 return null ;
192199 }
193200
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-
202201 if ( enhancementContext .isEntityClass ( managedCtClass ) ) {
203202 if ( checkUnsupportedAttributeNaming ( managedCtClass , enhancementContext ) ) {
204203 // do not enhance classes with mismatched names for PROPERTY-access persistent attributes
@@ -258,7 +257,7 @@ private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builde
258257
259258 builder = addInterceptorHandling ( builder , managedCtClass );
260259
261- if ( enhancementContext .doDirtyCheckingInline ( managedCtClass ) ) {
260+ if ( enhancementContext .doDirtyCheckingInline () ) {
262261 List <AnnotatedFieldDescription > collectionFields = collectCollectionFields ( managedCtClass );
263262
264263 if ( collectionFields .isEmpty () ) {
@@ -390,7 +389,7 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
390389 builder = builder .implement ( ManagedComposite .class );
391390 builder = addInterceptorHandling ( builder , managedCtClass );
392391
393- if ( enhancementContext .doDirtyCheckingInline ( managedCtClass ) ) {
392+ if ( enhancementContext .doDirtyCheckingInline () ) {
394393 builder = builder .implement ( CompositeTracker .class )
395394 .defineField (
396395 EnhancerConstants .TRACKER_COMPOSITE_FIELD_NAME ,
@@ -428,7 +427,7 @@ else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
428427 builder = builder .implement ( ManagedMappedSuperclass .class );
429428 return createTransformer ( managedCtClass ).applyTo ( builder );
430429 }
431- else if ( enhancementContext .doExtendedEnhancement ( managedCtClass ) ) {
430+ else if ( enhancementContext .doExtendedEnhancement () ) {
432431 log .tracef ( "Extended enhancement of [%s]" , managedCtClass .getName () );
433432 return createTransformer ( managedCtClass ).applyExtended ( builderSupplier .get () );
434433 }
@@ -438,6 +437,36 @@ else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
438437 }
439438 }
440439
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+
441470 /**
442471 * Utility that determines the access-type of a mapped class based on an explicit annotation
443472 * or guessing it from the placement of its identifier property. Implementation should be
@@ -629,31 +658,6 @@ private static boolean checkUnsupportedAttributeNaming(TypeDescription managedCt
629658 return new String ( chars );
630659 }
631660
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-
657661 private PersistentAttributeTransformer createTransformer (TypeDescription typeDescription ) {
658662 return PersistentAttributeTransformer .collectPersistentFields ( typeDescription , enhancementContext , typePool );
659663 }
@@ -872,4 +876,42 @@ else if ( access != null && access.load().value() == AccessType.FIELD ) {
872876 }
873877 }
874878
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+
875917}
0 commit comments