diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java index d1411e52ca31..6bf697630176 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BiDirectionalAssociationHandler.java @@ -47,7 +47,7 @@ static Implementation wrap( ByteBuddyEnhancementContext enhancementContext, AnnotatedFieldDescription persistentField, Implementation implementation) { - if ( !enhancementContext.doBiDirectionalAssociationManagement( persistentField ) ) { + if ( !enhancementContext.doBiDirectionalAssociationManagement() ) { return implementation; } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java index 842f071432f0..a36d22cba8e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java @@ -51,12 +51,12 @@ public boolean isMappedSuperclassClass(TypeDescription classDescriptor) { return enhancementContext.isMappedSuperclassClass( new UnloadedTypeDescription( classDescriptor ) ); } - public boolean doDirtyCheckingInline(TypeDescription classDescriptor) { - return enhancementContext.doDirtyCheckingInline( new UnloadedTypeDescription( classDescriptor ) ); + public boolean doDirtyCheckingInline() { + return enhancementContext.doDirtyCheckingInline(); } - public boolean doExtendedEnhancement(TypeDescription classDescriptor) { - return enhancementContext.doExtendedEnhancement( new UnloadedTypeDescription( classDescriptor ) ); + public boolean doExtendedEnhancement() { + return enhancementContext.doExtendedEnhancement(); } public boolean hasLazyLoadableAttributes(TypeDescription classDescriptor) { @@ -83,8 +83,8 @@ public boolean isMappedCollection(AnnotatedFieldDescription field) { return enhancementContext.isMappedCollection( field ); } - public boolean doBiDirectionalAssociationManagement(AnnotatedFieldDescription field) { - return enhancementContext.doBiDirectionalAssociationManagement( field ); + public boolean doBiDirectionalAssociationManagement() { + return enhancementContext.doBiDirectionalAssociationManagement(); } public boolean isDiscoveredType(TypeDescription typeDescription) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index 211d192dd442..133b5b624aea 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -30,7 +30,6 @@ import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.StubMethod; - import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.AssertionFailure; import org.hibernate.Version; @@ -76,15 +75,17 @@ import static net.bytebuddy.matcher.ElementMatchers.isStatic; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.not; +import static org.hibernate.bytecode.enhance.internal.bytebuddy.FeatureMismatchException.Feature.ASSOCIATION_MANAGEMENT; +import static org.hibernate.bytecode.enhance.internal.bytebuddy.FeatureMismatchException.Feature.DIRTY_CHECK; public class EnhancerImpl implements Enhancer { - private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class ); protected final ByteBuddyEnhancementContext enhancementContext; private final ByteBuddyState byteBuddyState; private final EnhancerClassLocator typePool; private final EnhancerImplConstants constants; + private final List infoAnnotationList; /** * Constructs the Enhancer, using the given context. @@ -109,8 +110,11 @@ public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddy this.byteBuddyState = Objects.requireNonNull( byteBuddyState ); this.typePool = Objects.requireNonNull( classLocator ); this.constants = byteBuddyState.getEnhancerConstants(); + + this.infoAnnotationList = List.of( createInfoAnnotation( enhancementContext ) ); } + /** * Performs the enhancement. * @@ -133,7 +137,7 @@ public byte[] enhance(String className, byte[] originalBytes) throws Enhancement return byteBuddyState.rewrite( typePool, safeClassName, byteBuddy -> doEnhance( () -> byteBuddy.ignore( isDefaultFinalizer() ) .redefine( typeDescription, typePool.asClassFileLocator() ) - .annotateType( constants.HIBERNATE_VERSION_ANNOTATION ), + .annotateType( infoAnnotationList ), typeDescription ) ); } @@ -167,14 +171,17 @@ public void discoverTypes(String className, byte[] originalBytes) { } private DynamicType.Builder doEnhance(Supplier> builderSupplier, TypeDescription managedCtClass) { - // skip if the class was already enhanced. This is very common in WildFly as classloading is highly concurrent. - // We need to ensure that no class is instrumented multiple times as that might result in incorrect bytecode. - // N.B. there is a second check below using a different approach: checking for the marker interfaces, - // which does not address the case of extended bytecode enhancement - // (because it enhances classes that do not end up with these marker interfaces). - // I'm currently inclined to keep both checks, as one is safer and the other has better backwards compatibility. - if ( managedCtClass.getDeclaredAnnotations().isAnnotationPresent( EnhancementInfo.class ) ) { - verifyVersions( managedCtClass, enhancementContext ); + if ( alreadyEnhanced( managedCtClass ) ) { + // the class already implements `Managed`. there are 2 broad cases - + // 1. the user manually implemented `Managed` + // 2. the class was previously enhanced + // in either case, look for `@EnhancementInfo` and, if found, verify we can "re-enhance" the class + final AnnotationDescription.Loadable infoAnnotation = managedCtClass.getDeclaredAnnotations().ofType( EnhancementInfo.class ); + if ( infoAnnotation != null ) { + // throws an exception if there is a mismatch... + verifyReEnhancement( managedCtClass, infoAnnotation.load(), enhancementContext ); + } + // verification succeeded (or not done) - we can simply skip the enhancement log.tracef( "Skipping enhancement of [%s]: it's already annotated with @EnhancementInfo", managedCtClass.getName() ); return null; } @@ -191,14 +198,6 @@ private DynamicType.Builder doEnhance(Supplier> builde return null; } - // handle already enhanced classes - if ( alreadyEnhanced( managedCtClass ) ) { - verifyVersions( managedCtClass, enhancementContext ); - - log.tracef( "Skipping enhancement of [%s]: it's already implementing 'Managed'", managedCtClass.getName() ); - return null; - } - if ( enhancementContext.isEntityClass( managedCtClass ) ) { if ( checkUnsupportedAttributeNaming( managedCtClass, enhancementContext ) ) { // do not enhance classes with mismatched names for PROPERTY-access persistent attributes @@ -258,7 +257,7 @@ private DynamicType.Builder doEnhance(Supplier> builde builder = addInterceptorHandling( builder, managedCtClass ); - if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { + if ( enhancementContext.doDirtyCheckingInline() ) { List collectionFields = collectCollectionFields( managedCtClass ); if ( collectionFields.isEmpty() ) { @@ -390,7 +389,7 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { builder = builder.implement( ManagedComposite.class ); builder = addInterceptorHandling( builder, managedCtClass ); - if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { + if ( enhancementContext.doDirtyCheckingInline() ) { builder = builder.implement( CompositeTracker.class ) .defineField( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, @@ -428,7 +427,7 @@ else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) { builder = builder.implement( ManagedMappedSuperclass.class ); return createTransformer( managedCtClass ).applyTo( builder ); } - else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { + else if ( enhancementContext.doExtendedEnhancement() ) { log.tracef( "Extended enhancement of [%s]", managedCtClass.getName() ); return createTransformer( managedCtClass ).applyExtended( builderSupplier.get() ); } @@ -438,6 +437,36 @@ else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { } } + private void verifyReEnhancement( + TypeDescription managedCtClass, + EnhancementInfo existingInfo, + ByteBuddyEnhancementContext enhancementContext) { + // first, make sure versions match + final String enhancementVersion = existingInfo.version(); + if ( "ignore".equals( enhancementVersion ) ) { + // for testing + log.debugf( "Skipping re-enhancement version check for %s due to `ignore`", managedCtClass.getName() ); + } + else if ( !Version.getVersionString().equals( enhancementVersion ) ) { + throw new VersionMismatchException( managedCtClass, enhancementVersion, Version.getVersionString() ); + } + + FeatureMismatchException.checkFeatureEnablement( + managedCtClass, + DIRTY_CHECK, + enhancementContext.doDirtyCheckingInline(), + existingInfo.includesDirtyChecking() + ); + + FeatureMismatchException.checkFeatureEnablement( + managedCtClass, + ASSOCIATION_MANAGEMENT, + enhancementContext.doBiDirectionalAssociationManagement(), + existingInfo.includesAssociationManagement() + ); + } + + /** * Utility that determines the access-type of a mapped class based on an explicit annotation * or guessing it from the placement of its identifier property. Implementation should be @@ -629,31 +658,6 @@ private static boolean checkUnsupportedAttributeNaming(TypeDescription managedCt return new String( chars ); } - private static void verifyVersions(TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext) { - final AnnotationDescription.Loadable existingInfo = managedCtClass - .getDeclaredAnnotations() - .ofType( EnhancementInfo.class ); - if ( existingInfo == null ) { - // There is an edge case here where a user manually adds `implement Managed` to - // their domain class, in which case there will most likely not be a - // `EnhancementInfo` annotation. Such cases should simply not do version checking. - // - // However, there is also ambiguity in this case with classes that were enhanced - // with old versions of Hibernate which did not add that annotation as part of - // enhancement. But overall we consider this condition to be acceptable - return; - } - - final String enhancementVersion = extractVersion( existingInfo ); - if ( !Version.getVersionString().equals( enhancementVersion ) ) { - throw new VersionMismatchException( managedCtClass, enhancementVersion, Version.getVersionString() ); - } - } - - private static String extractVersion(AnnotationDescription.Loadable annotation) { - return annotation.load().version(); - } - private PersistentAttributeTransformer createTransformer(TypeDescription typeDescription) { return PersistentAttributeTransformer.collectPersistentFields( typeDescription, enhancementContext, typePool ); } @@ -872,4 +876,42 @@ else if ( access != null && access.load().value() == AccessType.FIELD ) { } } + + private static EnhancementInfo createInfoAnnotation(EnhancementContext enhancementContext) { + return new EnhancementInfoImpl( enhancementContext.doDirtyCheckingInline(), enhancementContext.doBiDirectionalAssociationManagement() ); + } + + private static class EnhancementInfoImpl implements EnhancementInfo { + private final String version; + private final boolean includesDirtyChecking; + private final boolean includesAssociationManagement; + + public EnhancementInfoImpl(boolean includesDirtyChecking, boolean includesAssociationManagement) { + this.version = Version.getVersionString(); + this.includesDirtyChecking = includesDirtyChecking; + this.includesAssociationManagement = includesAssociationManagement; + } + + @Override + public String version() { + return version; + } + + @Override + public boolean includesDirtyChecking() { + return includesDirtyChecking; + } + + @Override + public boolean includesAssociationManagement() { + return includesAssociationManagement; + } + + @Override + public Class annotationType() { + return EnhancementInfo.class; + } + } + + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImplConstants.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImplConstants.java index 19a149ca42d9..c3fd43b6e0c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImplConstants.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImplConstants.java @@ -16,13 +16,10 @@ import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.StubMethod; -import java.lang.annotation.Annotation; import java.util.Collection; import java.util.List; -import org.hibernate.Version; import org.hibernate.bytecode.enhance.spi.CollectionTracker; -import org.hibernate.bytecode.enhance.spi.EnhancementInfo; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.ManagedEntity; import org.hibernate.engine.spi.PersistentAttributeInterceptor; @@ -60,17 +57,6 @@ public final class EnhancerImplConstants { //Frequently used annotations, declared as collections as otherwise they get wrapped into them over and over again: final Collection TRANSIENT_ANNOTATION = List.of( AnnotationDescription.Builder.ofType( Transient.class ).build() ); - final List HIBERNATE_VERSION_ANNOTATION = List.of( new EnhancementInfo() { - @Override - public String version() { - return Version.getVersionString(); - } - - @Override - public Class annotationType() { - return EnhancementInfo.class; - } - } ); //Frequently used Types for method signatures: final TypeDefinition TypeVoid = TypeDescription.ForLoadedType.of( void.class ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FeatureMismatchException.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FeatureMismatchException.java new file mode 100644 index 000000000000..2af141d27f9a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FeatureMismatchException.java @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import net.bytebuddy.description.type.TypeDescription; +import org.hibernate.bytecode.enhance.spi.EnhancementException; +import org.hibernate.bytecode.enhance.spi.EnhancementInfo; + +import java.util.Locale; + +/** + * Indicates a mismatch in either {@linkplain EnhancementInfo#includesDirtyChecking() dirty tracking} + * or {@linkplain EnhancementInfo#includesAssociationManagement() association management} + * between consecutive attempts to enhance a class. + * + * @author Steve Ebersole + */ +public class FeatureMismatchException extends EnhancementException { + public enum Feature { DIRTY_CHECK, ASSOCIATION_MANAGEMENT } + + private final String className; + private final Feature mismatchedFeature; + private final boolean previousValue; + + public FeatureMismatchException( + String className, + Feature mismatchedFeature, + boolean previousValue) { + super( String.format( + Locale.ROOT, + "Support for %s was enabled during enhancement, but `%s` was previously enhanced with that support %s.", + featureText( mismatchedFeature ), + className, + decode( previousValue ) + ) ); + this.className = className; + this.mismatchedFeature = mismatchedFeature; + this.previousValue = previousValue; + } + + public String getClassName() { + return className; + } + + public Feature getMismatchedFeature() { + return mismatchedFeature; + } + + public boolean wasPreviouslyEnabled() { + return previousValue; + } + + public static void checkFeatureEnablement( + TypeDescription managedCtClass, + Feature feature, + boolean currentlyEnabled, + boolean previouslyEnabled) { + if ( currentlyEnabled != previouslyEnabled ) { + throw new FeatureMismatchException( managedCtClass.getName(), feature, previouslyEnabled ); + } + } + + private static String featureText(Feature mismatchedFeature) { + return switch ( mismatchedFeature ) { + case DIRTY_CHECK -> "inline dirty checking"; + case ASSOCIATION_MANAGEMENT -> "bidirectional association management"; + }; + } + + private static String decode(boolean previousValue) { + return previousValue ? "enabled" : "disabled"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java index 7b24597d9d81..3eaede6d814f 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/InlineDirtyCheckingHandler.java @@ -57,7 +57,7 @@ static Implementation wrap( ByteBuddyEnhancementContext enhancementContext, AnnotatedFieldDescription persistentField, Implementation implementation) { - if ( enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { + if ( enhancementContext.doDirtyCheckingInline() ) { if ( enhancementContext.isCompositeClass( managedCtClass ) ) { implementation = Advice.to( CodeTemplates.CompositeDirtyCheckingHandler.class ).wrap( implementation ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index eae0f72b5264..4b068cb74276 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -281,7 +281,7 @@ DynamicType.Builder applyTo(DynamicType.Builder builder) { if ( !compositeOwner && !enhancementContext.isMappedSuperclassClass( managedCtClass ) && enhancementContext.isCompositeField( enhancedField ) - && enhancementContext.doDirtyCheckingInline( managedCtClass ) ) { + && enhancementContext.doDirtyCheckingInline() ) { compositeOwner = true; } } @@ -296,7 +296,7 @@ DynamicType.Builder applyTo(DynamicType.Builder builder) { } } - if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) { + if ( enhancementContext.doExtendedEnhancement() ) { builder = applyExtended( builder ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java index e17ed60e7186..13520a76a70f 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementContext.java @@ -23,7 +23,7 @@ * * @author Steve Ebersole */ -public interface EnhancementContext { +public interface EnhancementContext extends EnhancementOptions { /** * Obtain access to the ClassLoader that can be used to load Class references. In JPA SPI terms, this * should be a "temporary class loader" as defined by @@ -60,43 +60,6 @@ public interface EnhancementContext { */ boolean isMappedSuperclassClass(UnloadedClass classDescriptor); - /** - * Should we manage association of bidirectional persistent attributes for this field? - * - * @param field The field to check. - * - * @return {@code true} indicates that the field is enhanced so that for bidirectional persistent fields - * the association is managed, i.e. the associations are automatically set; {@code false} indicates that - * the management is handled by the user. - * @deprecated Will be removed without replacement. See HHH-19660 - */ - @Deprecated(forRemoval = true) - boolean doBiDirectionalAssociationManagement(UnloadedField field); - - /** - * Should we in-line dirty checking for persistent attributes for this class? - * - * @param classDescriptor The descriptor of the class to check. - * - * @return {@code true} indicates that dirty checking should be in-lined within the entity; {@code false} - * indicates it should not. In-lined is more easily serializable and probably more performant. - * @deprecated Will be removed without replacement. See HHH-15641 - */ - @Deprecated(forRemoval = true) - boolean doDirtyCheckingInline(UnloadedClass classDescriptor); - - /** - * Should we enhance field access to entities from this class? - * - * @param classDescriptor The descriptor of the class to check. - * - * @return {@code true} indicates that any direct access to fields of entities should be routed to the enhanced - * getter / setter method. - * @deprecated Will be removed without replacement. See HHH-19661 - */ - @Deprecated(forRemoval = true) - boolean doExtendedEnhancement(UnloadedClass classDescriptor); - /** * Does the given class define any lazy loadable attributes? * diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementInfo.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementInfo.java index 64cb38c30036..14e673b7ec7e 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementInfo.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementInfo.java @@ -11,13 +11,23 @@ /** * Provides basic information about the enhancement done to a class. + * Used to verify attempts to re-enhance an already enhanced class. */ @Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) public @interface EnhancementInfo { - /** * The Hibernate version used for enhancement. */ String version(); + + /** + * Whether dirty checking was enabled when the targeted class was enhanced. + */ + boolean includesDirtyChecking(); + + /** + * Whether bidirectional association management was enabled when the targeted class was enhanced. + */ + boolean includesAssociationManagement(); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementOptions.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementOptions.java new file mode 100644 index 000000000000..2b46f64447c5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/EnhancementOptions.java @@ -0,0 +1,81 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.bytecode.enhance.spi; + +/** + * Options for the {@linkplain Enhancer enhancement} process. + * + * @see EnhancementContext + * + * @author Steve Ebersole + */ +public interface EnhancementOptions { + /** + * Whether to enable support for inline dirtiness checking. + */ + default boolean doDirtyCheckingInline() { + return doDirtyCheckingInline( null ); + } + + /** + * Whether to enable support for extended enhancement. + * + * @deprecated Will be removed without replacement. See HHH-19661 + */ + @Deprecated(forRemoval = true, since = "7.1") + default boolean doExtendedEnhancement() { + return doExtendedEnhancement( null ); + } + + /** + * Whether to enable support for automatic management of bidirectional associations. + * + * @deprecated Will be removed without replacement. See HHH-19660 + */ + @Deprecated(forRemoval = true, since = "7.1") + default boolean doBiDirectionalAssociationManagement() { + return doBiDirectionalAssociationManagement( null ); + } + + /** + * Should we in-line dirty checking for persistent attributes for this class? + * + * @param classDescriptor The descriptor of the class to check. + * + * @return {@code true} indicates that dirty checking should be in-lined within the entity; {@code false} + * indicates it should not. In-lined is more easily serializable and probably more performant. + * + * @deprecated Use {@linkplain #doDirtyCheckingInline()} instead. + */ + @Deprecated(forRemoval = true) + boolean doDirtyCheckingInline(UnloadedClass classDescriptor); + + /** + * Should we enhance field access to entities from this class? + * + * @param classDescriptor The descriptor of the class to check. + * + * @return {@code true} indicates that any direct access to fields of entities should be routed to the enhanced + * getter / setter method. + * + * @deprecated Use {@linkplain #doExtendedEnhancement()} instead. + */ + @Deprecated(forRemoval = true, since = "7.1") + boolean doExtendedEnhancement(UnloadedClass classDescriptor); + + /** + * Whether to enable support for automatic management of bidirectional associations for this field. + * + * @param field The field to check. + * + * @return {@code true} indicates that the field is enhanced so that for bidirectional persistent fields + * the association is managed, i.e. the associations are automatically set; {@code false} indicates that + * the management is handled by the user. + * + * @deprecated Use {@linkplain #doBiDirectionalAssociationManagement()} instead. + */ + @Deprecated(forRemoval = true, since = "7.1") + boolean doBiDirectionalAssociationManagement(UnloadedField field); +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/EnhancementInfoAnnotation.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/EnhancementInfoAnnotation.java new file mode 100644 index 000000000000..2450b0864b9d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/EnhancementInfoAnnotation.java @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhance.version; + +import org.hibernate.Version; +import org.hibernate.bytecode.enhance.spi.EnhancementInfo; + +import java.lang.annotation.Annotation; + +/** + * @author Steve Ebersole + */ +public class EnhancementInfoAnnotation implements EnhancementInfo { + private String version; + private boolean includesDirtyChecking; + private boolean includesAssociationManagement; + + public EnhancementInfoAnnotation() { + this( Version.getVersionString() ); + } + + public EnhancementInfoAnnotation(String version) { + this( version, false, false ); + } + + public EnhancementInfoAnnotation( + String version, + boolean includesDirtyChecking, + boolean includesAssociationManagement) { + this.version = version; + this.includesDirtyChecking = includesDirtyChecking; + this.includesAssociationManagement = includesAssociationManagement; + } + + @Override + public String version() { + return version; + } + + @Override + public boolean includesDirtyChecking() { + return includesDirtyChecking; + } + + public void setVersion(String version) { + this.version = version; + } + + @Override + public boolean includesAssociationManagement() { + return includesAssociationManagement; + } + + public void setIncludesDirtyChecking(boolean includesDirtyChecking) { + this.includesDirtyChecking = includesDirtyChecking; + } + + @Override + public Class annotationType() { + return EnhancementInfo.class; + } + + public void setIncludesAssociationManagement(boolean includesAssociationManagement) { + this.includesAssociationManagement = includesAssociationManagement; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/ReEnhancementTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/ReEnhancementTests.java new file mode 100644 index 000000000000..469f4548ce3f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/ReEnhancementTests.java @@ -0,0 +1,107 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhance.version; + +import org.hibernate.bytecode.enhance.VersionMismatchException; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; +import org.hibernate.bytecode.enhance.internal.bytebuddy.FeatureMismatchException; +import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; +import org.hibernate.bytecode.enhance.spi.Enhancer; +import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; +import org.hibernate.bytecode.spi.ByteCodeHelper; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +/** + * @author Steve Ebersole + */ +public class ReEnhancementTests { + @Test + void testVersionMismatch() { + final DefaultEnhancementContext enhancementContext = new DefaultEnhancementContext(); + final ByteBuddyState byteBuddyState = new ByteBuddyState(); + final Enhancer enhancer = new EnhancerImpl( enhancementContext, byteBuddyState ); + + try { + attemptEnhancement( SimpleEntity.class, enhancer ); + fail( "Expecting a VersionMismatchException" ); + } + catch (VersionMismatchException expected) { + // expected + } + } + + @Test + void testDirtyCheckingSettingMismatch() { + final DefaultEnhancementContext enhancementContext = new DefaultEnhancementContext() { + @Override + public boolean doDirtyCheckingInline() { + return true; + } + + @Override + public boolean doBiDirectionalAssociationManagement() { + return false; + } + }; + final ByteBuddyState byteBuddyState = new ByteBuddyState(); + final Enhancer enhancer = new EnhancerImpl( enhancementContext, byteBuddyState ); + + try { + attemptEnhancement( SimpleEntity2.class, enhancer ); + fail( "Expecting a FeatureMismatchException" ); + } + catch (FeatureMismatchException expected) { + // expected + assertThat( expected.getMessage() ).contains( "inline dirty checking" ); + assertThat( expected.getMessage() ).endsWith( "was previously enhanced with that support disabled." ); + } + } + + @Test + void testAssociationManagementSettingMismatch() { + final DefaultEnhancementContext enhancementContext = new DefaultEnhancementContext() { + @Override + public boolean doDirtyCheckingInline() { + return false; + } + + @Override + public boolean doBiDirectionalAssociationManagement() { + return true; + } + }; + final ByteBuddyState byteBuddyState = new ByteBuddyState(); + final Enhancer enhancer = new EnhancerImpl( enhancementContext, byteBuddyState ); + + try { + attemptEnhancement( SimpleEntity2.class, enhancer ); + fail( "Expecting a FeatureMismatchException" ); + } + catch (FeatureMismatchException expected) { + // expected + assertThat( expected.getMessage() ).contains( "bidirectional association management" ); + assertThat( expected.getMessage() ).endsWith( "was previously enhanced with that support disabled." ); + } + } + + private void attemptEnhancement(Class clazz, Enhancer enhancer) { + final String classFileName = clazz.getName().replace( '.', '/' ) + ".class"; + try (InputStream classFileStream = clazz.getClassLoader().getResourceAsStream( classFileName ) ) { + final byte[] originalBytes = ByteCodeHelper.readByteCode( classFileStream ); + enhancer.enhance( clazz.getName(), originalBytes ); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/SimpleEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/SimpleEntity.java index 6b0797e9427f..4e6a470b47d5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/SimpleEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/SimpleEntity.java @@ -17,7 +17,7 @@ */ @Entity(name = "SimpleEntity") @Table(name = "SimpleEntity") -@EnhancementInfo(version = "5.3.0.Final") +@EnhancementInfo(version = "5.3.0.Final", includesDirtyChecking = false, includesAssociationManagement = false) public class SimpleEntity implements ManagedEntity { @Id private Integer id; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/SimpleEntity2.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/SimpleEntity2.java new file mode 100644 index 000000000000..272d93efad7f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/SimpleEntity2.java @@ -0,0 +1,79 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.bytecode.enhance.version; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import org.hibernate.bytecode.enhance.spi.EnhancementInfo; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ManagedEntity; + +/** + * @author Steve Ebersole + */ +@Entity(name = "SimpleEntity") +@Table(name = "SimpleEntity") +@EnhancementInfo(version = "ignore", includesDirtyChecking = false, includesAssociationManagement = false) +public class SimpleEntity2 implements ManagedEntity { + @Id + private Integer id; + private String name; + + @Override + public Object $$_hibernate_getEntityInstance() { + return null; + } + + @Override + public EntityEntry $$_hibernate_getEntityEntry() { + return null; + } + + @Override + public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) { + + } + + @Override + public ManagedEntity $$_hibernate_getPreviousManagedEntity() { + return null; + } + + @Override + public void $$_hibernate_setPreviousManagedEntity(ManagedEntity previous) { + + } + + @Override + public ManagedEntity $$_hibernate_getNextManagedEntity() { + return null; + } + + @Override + public void $$_hibernate_setNextManagedEntity(ManagedEntity next) { + + } + + @Override + public void $$_hibernate_setUseTracker(boolean useTracker) { + + } + + @Override + public boolean $$_hibernate_useTracker() { + return false; + } + + @Override + public int $$_hibernate_getInstanceId() { + return 0; + } + + @Override + public void $$_hibernate_setInstanceId(int id) { + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/VersionMismatchTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/VersionMismatchTests.java deleted file mode 100644 index 44f635d7396f..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhance/version/VersionMismatchTests.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.orm.test.bytecode.enhance.version; - -import java.io.IOException; -import java.io.InputStream; - -import org.hibernate.bytecode.enhance.VersionMismatchException; -import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; -import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext; -import org.hibernate.bytecode.enhance.spi.Enhancer; -import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; -import org.hibernate.bytecode.spi.ByteCodeHelper; - -import org.hibernate.testing.orm.junit.Jira; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.fail; - -/** - * @author Steve Ebersole - */ -@Jira( "https://hibernate.atlassian.net/browse/HHH-16529" ) -public class VersionMismatchTests { - @Test - void testVersionMismatch() throws IOException { - final DefaultEnhancementContext enhancementContext = new DefaultEnhancementContext(); - final ByteBuddyState byteBuddyState = new ByteBuddyState(); - final Enhancer enhancer = new EnhancerImpl( enhancementContext, byteBuddyState ); - - final String classFile = SimpleEntity.class.getName().replace( '.', '/' ) + ".class"; - try (InputStream classFileStream = SimpleEntity.class.getClassLoader().getResourceAsStream( classFile )) { - final byte[] originalBytes = ByteCodeHelper.readByteCode( classFileStream ); - enhancer.enhance( SimpleEntity.class.getName(), originalBytes ); - fail( "Expecting failure" ); - } - catch (VersionMismatchException expected) { - // expected - } - } - -}