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 0b1f68e60160..5e495b30f1ad 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 @@ -7,6 +7,7 @@ package org.hibernate.bytecode.enhance.internal.bytebuddy; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @@ -35,10 +36,16 @@ class ByteBuddyEnhancementContext { private final ConcurrentHashMap> getterByTypeMap = new ConcurrentHashMap<>(); private final ConcurrentHashMap locksMap = new ConcurrentHashMap<>(); - ByteBuddyEnhancementContext(EnhancementContext enhancementContext) { - this.enhancementContext = enhancementContext; + ByteBuddyEnhancementContext(final EnhancementContext enhancementContext) { + this.enhancementContext = Objects.requireNonNull( enhancementContext ); } + /** + * @deprecated as it's currently unused and we're not always actually sourcing the classes to be transformed + * from a classloader, so this getter can't always be honoured correctly. + * @return the ClassLoader provided by the underlying EnhancementContext. Might be otherwise ignored. + */ + @Deprecated(forRemoval = true) public ClassLoader getLoadingClassLoader() { return enhancementContext.getLoadingClassLoader(); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CoreTypePool.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CoreTypePool.java new file mode 100644 index 000000000000..87dbc33729d9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CoreTypePool.java @@ -0,0 +1,98 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import java.util.concurrent.ConcurrentHashMap; + +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.pool.TypePool; + +/** + * A TypePool which only loads, and caches, types whose package + * name starts with certain chosen prefixes. + * The default is to only load classes whose package names start with + * either "jakarta." or "java.". + * This allows to reuse these caches independently from application + * code and classloader changes, as during enhancement we frequently + * encounter such symbols as well, for example triggered by JPA annotations + * or properties mapped via standard java types and collections. + * Symbols resolved by this pool are backed by loaded classes from + * ORM's classloader. + */ +public class CoreTypePool extends TypePool.AbstractBase implements TypePool { + + private final ClassLoader hibernateClassLoader = CoreTypePool.class.getClassLoader(); + private final ConcurrentHashMap resolutions = new ConcurrentHashMap<>(); + private final String[] acceptedPrefixes; + + /** + * Construct a new {@link CoreTypePool} with its default configuration: + * to only load classes whose package names start with either "jakarta." + * or "java." + */ + public CoreTypePool() { + //By default optimise for jakarta annotations, and java util collections + this("jakarta.", "java.", "org.hibernate.annotations."); + } + + /** + * Construct a new {@link CoreTypePool} with a choice of which prefixes + * for fully qualified classnames will be loaded by this {@link TypePool}. + */ + public CoreTypePool(final String... acceptedPrefixes) { + //While we implement a cache in this class we also want to enable + //ByteBuddy's default caching mechanism as it will cache the more + //useful output of the parsing and introspection of such types. + super( new TypePool.CacheProvider.Simple() ); + this.acceptedPrefixes = acceptedPrefixes; + } + + private boolean isCoreClassName(final String name) { + for ( String acceptedPrefix : this.acceptedPrefixes ) { + if ( name.startsWith( acceptedPrefix ) ) { + return true; + } + } + return false; + } + + @Override + protected Resolution doDescribe(final String name) { + if ( isCoreClassName( name ) ) { + final Resolution resolution = resolutions.get( name ); + if ( resolution != null ) { + return resolution; + } + else { + //We implement this additional layer of caching, which is on top of + //ByteBuddy's default caching, so as to prevent resolving the same + //types concurrently from the classloader. + //This is merely an efficiency improvement and will NOT provide a + //strict guarantee of symbols being resolved exactly once as there + //is no SPI within ByteBuddy which would allow this: the point is to + //make it exceptionally infrequent, which greatly helps with + //processing of large models. + return resolutions.computeIfAbsent( name, this::actualResolve ); + } + } + else { + //These are not cached to not leak references to application code names + return new Resolution.Illegal( name ); + } + } + + private Resolution actualResolve(final String name) { + try { + final Class aClass = Class.forName( name, false, hibernateClassLoader ); + return new TypePool.Resolution.Simple( TypeDescription.ForLoadedType.of( aClass ) ); + } + catch ( ClassNotFoundException e ) { + return new Resolution.Illegal( name ); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerClassLocator.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerClassLocator.java new file mode 100644 index 000000000000..8724026ca5aa --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerClassLocator.java @@ -0,0 +1,36 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import net.bytebuddy.dynamic.ClassFileLocator; +import net.bytebuddy.pool.TypePool; + +/** + * Extends the TypePool contract of ByteBuddy with our additional needs. + */ +public interface EnhancerClassLocator extends TypePool { + + /** + * Register a new class to the locator explicitly. + * @param className + * @param originalBytes + */ + void registerClassNameAndBytes(String className, byte[] originalBytes); + + /** + * This can optionally be used to remove an explicit mapping when it's no longer + * essential to retain it. + * The underlying implementation might ignore the operation. + * @param className + */ + void deregisterClassNameAndBytes(String className); + + /** + * @return the underlying {@link ClassFileLocator} + */ + ClassFileLocator asClassFileLocator(); +} 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 715100ff8a91..38740fba2a9c 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 @@ -6,7 +6,6 @@ */ package org.hibernate.bytecode.enhance.internal.bytebuddy; -import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -14,8 +13,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import org.hibernate.Version; @@ -66,7 +65,6 @@ import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.StubMethod; -import net.bytebuddy.pool.TypePool; import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer; @@ -93,9 +91,7 @@ public Class annotationType() { protected final ByteBuddyEnhancementContext enhancementContext; private final ByteBuddyState byteBuddyState; - - private final EnhancerClassFileLocator classFileLocator; - private final TypePool typePool; + private final EnhancerClassLocator typePool; /** * Extract the following constants so that enhancement on large projects @@ -117,6 +113,7 @@ public Class annotationType() { private final Advice adviceInitializeLazyAttributeLoadingInterceptor = Advice.to( CodeTemplates.InitializeLazyAttributeLoadingInterceptor.class, adviceLocator ); private final Implementation implementationSetOwner = Advice.to( CodeTemplates.SetOwner.class, adviceLocator ).wrap( StubMethod.INSTANCE ); private final Implementation implementationClearOwner = Advice.to( CodeTemplates.ClearOwner.class, adviceLocator ).wrap( StubMethod.INSTANCE ); + private final EnhancerImplConstants constants; /** * Constructs the Enhancer, using the given context. @@ -126,10 +123,21 @@ public Class annotationType() { * @param byteBuddyState refers to the ByteBuddy instance to use */ public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState byteBuddyState) { + this( enhancementContext, byteBuddyState, ModelTypePool.buildModelTypePool( enhancementContext.getLoadingClassLoader() ) ); + } + + /** + * Expert level constructor, this allows for more control of state and bytecode loading, + * which allows integrators to optimise for particular contexts of use. + * @param enhancementContext + * @param byteBuddyState + * @param classLocator + */ + public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState byteBuddyState, final EnhancerClassLocator classLocator) { this.enhancementContext = new ByteBuddyEnhancementContext( enhancementContext ); - this.byteBuddyState = byteBuddyState; - this.classFileLocator = new EnhancerClassFileLocator( enhancementContext.getLoadingClassLoader() ); - this.typePool = buildTypePool( classFileLocator ); + this.byteBuddyState = Objects.requireNonNull( byteBuddyState ); + this.typePool = Objects.requireNonNull( classLocator ); + this.constants = byteBuddyState.getEnhancerConstants(); } /** @@ -147,13 +155,13 @@ public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddy public byte[] enhance(String className, byte[] originalBytes) throws EnhancementException { //Classpool#describe does not accept '/' in the description name as it expects a class name. See HHH-12545 final String safeClassName = className.replace( '/', '.' ); - classFileLocator.registerClassNameAndBytes( safeClassName, originalBytes ); + typePool.registerClassNameAndBytes( safeClassName, originalBytes ); try { final TypeDescription typeDescription = typePool.describe( safeClassName ).resolve(); return byteBuddyState.rewrite( typePool, safeClassName, byteBuddy -> doEnhance( () -> byteBuddy.ignore( isDefaultFinalizer() ) - .redefine( typeDescription, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ) + .redefine( typeDescription, typePool.asClassFileLocator() ) .annotateType( HIBERNATE_VERSION_ANNOTATION ), typeDescription ) ); @@ -165,14 +173,14 @@ public byte[] enhance(String className, byte[] originalBytes) throws Enhancement throw new EnhancementException( "Failed to enhance class " + className, e ); } finally { - classFileLocator.deregisterClassNameAndBytes( safeClassName ); + typePool.deregisterClassNameAndBytes( safeClassName ); } } @Override public void discoverTypes(String className, byte[] originalBytes) { if ( originalBytes != null ) { - classFileLocator.registerClassNameAndBytes( className, originalBytes ); + typePool.registerClassNameAndBytes( className, originalBytes ); } try { final TypeDescription typeDescription = typePool.describe( className ).resolve(); @@ -183,14 +191,10 @@ public void discoverTypes(String className, byte[] originalBytes) { throw new EnhancementException( "Failed to discover types for class " + className, e ); } finally { - classFileLocator.deregisterClassNameAndBytes( className ); + typePool.deregisterClassNameAndBytes( className ); } } - private TypePool buildTypePool(final ClassFileLocator classFileLocator) { - return TypePool.Default.WithLazyResolution.of( classFileLocator ); - } - private DynamicType.Builder doEnhance(Supplier> builderSupplier, TypeDescription managedCtClass) { // can't effectively enhance interfaces if ( managedCtClass.isInterface() ) { @@ -260,18 +264,18 @@ private DynamicType.Builder doEnhance(Supplier> builde .annotateField( TRANSIENT_ANNOTATION ) .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) .withParameters( String.class ) - .intercept( implementationTrackChange ) + .intercept( constants.implementationTrackChange ) .defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC ) - .intercept( implementationGetDirtyAttributesWithoutCollections ) + .intercept( constants.implementationGetDirtyAttributesWithoutCollections ) .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) - .intercept( implementationAreFieldsDirtyWithoutCollections ) + .intercept( constants.implementationAreFieldsDirtyWithoutCollections ) .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( implementationClearDirtyAttributesWithoutCollections ) + .intercept( constants.implementationClearDirtyAttributesWithoutCollections ) .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC ) .withParameters( boolean.class ) - .intercept( implementationSuspendDirtyTracking ) + .intercept( constants.implementationSuspendDirtyTracking ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) - .intercept( implementationGetCollectionTrackerWithoutCollections ); + .intercept( constants.implementationGetCollectionTrackerWithoutCollections ); } else { //TODO es.enableInterfaceExtendedSelfDirtinessTracker ? Careful with consequences.. @@ -282,16 +286,16 @@ private DynamicType.Builder doEnhance(Supplier> builde .annotateField( TRANSIENT_ANNOTATION ) .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) .withParameters( String.class ) - .intercept( implementationTrackChange ) + .intercept( constants.implementationTrackChange ) .defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC ) - .intercept( implementationGetDirtyAttributes ) + .intercept( constants.implementationGetDirtyAttributes ) .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) - .intercept( implementationAreFieldsDirty ) + .intercept( constants.implementationAreFieldsDirty ) .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC ) - .intercept( implementationClearDirtyAttributes ) + .intercept( constants.implementationClearDirtyAttributes ) .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC ) .withParameters( boolean.class ) - .intercept( implementationSuspendDirtyTracking ) + .intercept( constants.implementationSuspendDirtyTracking ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC ) .intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) ); @@ -316,17 +320,17 @@ private DynamicType.Builder doEnhance(Supplier> builde isDirty = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionFieldName ) .bind( CodeTemplates.FieldValue.class, fieldDescription ) - .to( adviceIsDirty, adviceLocator ) + .to( adviceIsDirty, constants.adviceLocator ) .wrap( isDirty ); getDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionFieldName ) .bind( CodeTemplates.FieldValue.class, fieldDescription ) - .to( adviceGetDirtyNames, adviceLocator ) + .to( adviceGetDirtyNames, constants.adviceLocator ) .wrap( getDirtyNames ); clearDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionFieldName ) .bind( CodeTemplates.FieldValue.class, fieldDescription ) - .to( adviceClearDirtyNames, adviceLocator ) + .to( adviceClearDirtyNames, constants.adviceLocator ) .wrap( clearDirtyNames ); } else { @@ -335,23 +339,23 @@ private DynamicType.Builder doEnhance(Supplier> builde isDirty = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionFieldName ) .bind( CodeTemplates.FieldValue.class, getterMapping ) - .to( adviceIsDirty, adviceLocator ) + .to( adviceIsDirty, constants.adviceLocator ) .wrap( isDirty ); getDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionFieldName ) .bind( CodeTemplates.FieldValue.class, getterMapping ) - .to( adviceGetDirtyNames, adviceLocator ) + .to( adviceGetDirtyNames, constants.adviceLocator ) .wrap( getDirtyNames ); clearDirtyNames = Advice.withCustomMapping() .bind( CodeTemplates.FieldName.class, collectionFieldName ) .bind( CodeTemplates.FieldValue.class, getterMapping ) - .to( adviceClearDirtyNames, adviceLocator ) + .to( adviceClearDirtyNames, constants.adviceLocator ) .wrap( clearDirtyNames ); } } if ( enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) { - clearDirtyNames = adviceInitializeLazyAttributeLoadingInterceptor.wrap( clearDirtyNames ); + clearDirtyNames = constants.adviceInitializeLazyAttributeLoadingInterceptor.wrap( clearDirtyNames ); } builder = builder.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, boolean.class, Visibility.PUBLIC ) @@ -361,7 +365,7 @@ private DynamicType.Builder doEnhance(Supplier> builde .intercept( getDirtyNames ) .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, void.class, Visibility.PUBLIC ) .intercept( Advice.withCustomMapping() - .to( CodeTemplates.ClearDirtyCollectionNames.class, adviceLocator ) + .to( CodeTemplates.ClearDirtyCollectionNames.class, constants.adviceLocator ) .wrap( StubMethod.INSTANCE ) ) .defineMethod( ExtendedSelfDirtinessTracker.REMOVE_DIRTY_FIELDS_NAME, void.class, Visibility.PUBLIC ) .withParameters( LazyAttributeLoadingInterceptor.class ) @@ -393,14 +397,14 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { Visibility.PUBLIC ) .withParameters( String.class, CompositeOwner.class ) - .intercept( implementationSetOwner ) + .intercept( constants.implementationSetOwner ) .defineMethod( EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER, void.class, Visibility.PUBLIC ) .withParameters( String.class ) - .intercept( implementationClearOwner ); + .intercept( constants.implementationClearOwner ); } return createTransformer( managedCtClass ).applyTo( builder ); @@ -652,39 +656,4 @@ else if ( access != null && access.load().value() == AccessType.FIELD ) { } } - private static class EnhancerClassFileLocator extends ClassFileLocator.ForClassLoader { - private final ConcurrentHashMap resolutions = new ConcurrentHashMap<>(); - - /** - * Creates a new class file locator for the given class loader. - * - * @param classLoader The class loader to query which must not be the bootstrap class loader, i.e. {@code null}. - */ - protected EnhancerClassFileLocator(ClassLoader classLoader) { - super( classLoader ); - } - - @Override - public Resolution locate(String className) throws IOException { - assert className != null; - final Resolution resolution = resolutions.get( className ); - if ( resolution != null ) { - return resolution; - } - else { - return super.locate( className ); - } - } - - void registerClassNameAndBytes(String className, byte[] bytes) { - assert className != null; - assert bytes != null; - resolutions.put( className, new Resolution.Explicit( bytes ) ); - } - - void deregisterClassNameAndBytes(String className) { - resolutions.remove( className ); - } - } - } 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 new file mode 100644 index 000000000000..79ec6eb9fcec --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImplConstants.java @@ -0,0 +1,77 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import net.bytebuddy.asm.Advice; +import net.bytebuddy.dynamic.ClassFileLocator; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.StubMethod; + +/** + * Extracts constants used by EnhancerImpl. + * This allows integrators to choose reusing this state for multiple enhancement processes, + * as these are fairly expensive to initialize, or rather choose to free memory by + * not retaining this. + */ +public final class EnhancerImplConstants { + + final ClassFileLocator adviceLocator; + + final Implementation implementationTrackChange; + final Implementation implementationGetDirtyAttributesWithoutCollections; + final Implementation implementationAreFieldsDirtyWithoutCollections; + final Implementation implementationClearDirtyAttributesWithoutCollections; + final Implementation implementationSuspendDirtyTracking; + final Implementation implementationGetDirtyAttributes; + final Implementation implementationAreFieldsDirty; + final Implementation implementationGetCollectionTrackerWithoutCollections; + final Implementation implementationClearDirtyAttributes; + //In this case we just extract the Advice: + final Advice adviceInitializeLazyAttributeLoadingInterceptor; + final Implementation implementationSetOwner; + final Implementation implementationClearOwner; + + public EnhancerImplConstants() { + this.adviceLocator = ClassFileLocator.ForClassLoader.of( CodeTemplates.class.getClassLoader() ); + this.implementationTrackChange = Advice.to( CodeTemplates.TrackChange.class, adviceLocator ) + .wrap( StubMethod.INSTANCE ); + this.implementationGetDirtyAttributesWithoutCollections = Advice.to( + CodeTemplates.GetDirtyAttributesWithoutCollections.class, + adviceLocator + ).wrap( StubMethod.INSTANCE ); + this.implementationAreFieldsDirtyWithoutCollections = Advice.to( + CodeTemplates.AreFieldsDirtyWithoutCollections.class, + adviceLocator + ).wrap( StubMethod.INSTANCE ); + this.implementationClearDirtyAttributesWithoutCollections = Advice.to( + CodeTemplates.ClearDirtyAttributesWithoutCollections.class, + adviceLocator + ).wrap( StubMethod.INSTANCE ); + this.implementationSuspendDirtyTracking = Advice.to( CodeTemplates.SuspendDirtyTracking.class, adviceLocator ) + .wrap( StubMethod.INSTANCE ); + this.implementationGetDirtyAttributes = Advice.to( CodeTemplates.GetDirtyAttributes.class, adviceLocator ).wrap( + StubMethod.INSTANCE ); + this.implementationAreFieldsDirty = Advice.to( CodeTemplates.AreFieldsDirty.class, adviceLocator ).wrap( + StubMethod.INSTANCE ); + this.implementationGetCollectionTrackerWithoutCollections = Advice.to( + CodeTemplates.GetCollectionTrackerWithoutCollections.class, + adviceLocator + ).wrap( StubMethod.INSTANCE ); + this.implementationClearDirtyAttributes = Advice.to( CodeTemplates.ClearDirtyAttributes.class, adviceLocator ) + .wrap( StubMethod.INSTANCE ); + //In this case we just extract the Advice: + this.adviceInitializeLazyAttributeLoadingInterceptor = Advice.to( + CodeTemplates.InitializeLazyAttributeLoadingInterceptor.class, + adviceLocator + ); + this.implementationSetOwner = Advice.to( CodeTemplates.SetOwner.class, adviceLocator ) + .wrap( StubMethod.INSTANCE ); + this.implementationClearOwner = Advice.to( CodeTemplates.ClearOwner.class, adviceLocator ) + .wrap( StubMethod.INSTANCE ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ModelTypePool.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ModelTypePool.java new file mode 100644 index 000000000000..ecbc1ede0fea --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ModelTypePool.java @@ -0,0 +1,108 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import net.bytebuddy.dynamic.ClassFileLocator; +import net.bytebuddy.pool.TypePool; + +/** + * A TypePool suitable for loading user's classes, + * potentially in parallel operations. + */ +public class ModelTypePool extends TypePool.Default implements EnhancerClassLocator { + + private final ConcurrentHashMap resolutions = new ConcurrentHashMap<>(); + private final OverridingClassFileLocator locator; + + private ModelTypePool(CacheProvider cacheProvider, OverridingClassFileLocator classFileLocator, CoreTypePool parent) { + super( cacheProvider, classFileLocator, ReaderMode.FAST, parent ); + this.locator = classFileLocator; + } + + /** + * Creates a new empty EnhancerClassLocator instance which will load any application + * classes that need being reflected on from the ClassLoader passed as parameter. + * This TypePool will delegate, parent first, to a newly constructed empty instance + * of CoreTypePool; this parent pool will be used to load non-application types from + * the Hibernate classloader instead, not the one specified as argument. + * @see CoreTypePool + * @param classLoader + * @return the newly created EnhancerClassLocator + */ + public static EnhancerClassLocator buildModelTypePool(ClassLoader classLoader) { + return buildModelTypePool( ClassFileLocator.ForClassLoader.of( classLoader ) ); + } + + /** + * Similar to {@link #buildModelTypePool(ClassLoader)} except the application classes + * are not necessarily sourced from a standard classloader: it accepts a {@link ClassFileLocator}, + * which offers some more flexibility. + * @param classFileLocator + * @return the newly created EnhancerClassLocator + */ + public static EnhancerClassLocator buildModelTypePool(ClassFileLocator classFileLocator) { + return buildModelTypePool( classFileLocator, new CoreTypePool() ); + } + + /** + * Similar to {@link #buildModelTypePool(ClassFileLocator)} but allows specifying an existing + * {@link CoreTypePool} to be used as parent pool. + * This forms allows constructing a custom CoreTypePool and also separated the cache of the parent pool, + * which might be useful to reuse for multiple enhancement processes while desiring a clean new + * state for the {@link ModelTypePool}. + * @param classFileLocator + * @param coreTypePool + * @return + */ + public static EnhancerClassLocator buildModelTypePool(ClassFileLocator classFileLocator, CoreTypePool coreTypePool) { + return buildModelTypePool( classFileLocator, coreTypePool, new TypePool.CacheProvider.Simple() ); + } + + /** + * The more advanced strategy to construct a new ModelTypePool, allowing customization of all its aspects. + * @param classFileLocator + * @param coreTypePool + * @param cacheProvider + * @return + */ + public static EnhancerClassLocator buildModelTypePool(ClassFileLocator classFileLocator, CoreTypePool coreTypePool, CacheProvider cacheProvider) { + Objects.requireNonNull( classFileLocator ); + Objects.requireNonNull( coreTypePool ); + Objects.requireNonNull( cacheProvider ); + return new ModelTypePool( cacheProvider, new OverridingClassFileLocator( classFileLocator ), coreTypePool ); + } + + @Override + protected Resolution doDescribe(final String name) { + final Resolution resolution = resolutions.get( name ); + if ( resolution != null ) { + return resolution; + } + else { + return resolutions.computeIfAbsent( name, super::doDescribe ); + } + } + + @Override + public void registerClassNameAndBytes(final String className, final byte[] bytes) { + locator.put( className, new ClassFileLocator.Resolution.Explicit( Objects.requireNonNull( bytes ) ) ); + } + + @Override + public void deregisterClassNameAndBytes(final String className) { + locator.remove( className ); + } + + @Override + public ClassFileLocator asClassFileLocator() { + return locator; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/OverridingClassFileLocator.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/OverridingClassFileLocator.java new file mode 100644 index 000000000000..2f64dabbdd70 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/OverridingClassFileLocator.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import java.io.IOException; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import net.bytebuddy.dynamic.ClassFileLocator; + +/** + * Allows wrapping another ClassFileLocator to add the ability to define + * resolution overrides for specific resources. + */ +public final class OverridingClassFileLocator implements ClassFileLocator { + + private final ConcurrentHashMap registeredResolutions = new ConcurrentHashMap<>(); + private final ClassFileLocator parent; + + public OverridingClassFileLocator(final ClassFileLocator parent) { + this.parent = Objects.requireNonNull( parent ); + } + + @Override + public Resolution locate(final String name) throws IOException { + final Resolution resolution = registeredResolutions.get( name ); + if ( resolution != null ) { + return resolution; + } + else { + return parent.locate( name ); + } + } + + @Override + public void close() throws IOException { + //Nothing to do: we're not responsible for parent + } + + void put(String className, Resolution.Explicit explicit) { + registeredResolutions.put( className, explicit ); + } + + void remove(String className) { + registeredResolutions.remove( className ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index bc3b0779da8a..f513da85af17 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -28,6 +28,7 @@ import java.util.function.Function; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImplConstants; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.bytecode.spi.BasicProxyFactory; import org.hibernate.engine.spi.PrimeAmongSecondarySupertypes; @@ -72,6 +73,8 @@ public final class ByteBuddyState { private final ClassRewriter classRewriter; + final EnhancerImplConstants enhancerConstants = new EnhancerImplConstants(); + /** * It will be easier to maintain the cache and its state when it will no longer be static * in Hibernate ORM 6+. @@ -254,6 +257,10 @@ private Unloaded make(TypePool typePool, DynamicType.Builder builder) { return unloadedClass; } + public EnhancerImplConstants getEnhancerConstants() { + return this.enhancerConstants; + } + private static class GetDeclaredMethodAction implements PrivilegedAction { private final Class clazz; private final String methodName; diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index d5088c0424db..392793881c6d 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -21,6 +21,7 @@ import java.util.concurrent.Callable; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerClassLocator; import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.Enhancer; @@ -67,6 +68,7 @@ import net.bytebuddy.jar.asm.Type; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; +import net.bytebuddy.pool.TypePool; import org.checkerframework.checker.nullness.qual.Nullable; public class BytecodeProviderImpl implements BytecodeProvider { @@ -1312,6 +1314,18 @@ public String[] call() { return new EnhancerImpl( enhancementContext, byteBuddyState ); } + /** + * Similar to {@link #getEnhancer(EnhancementContext)} but intended for advanced users who wish + * to customize how ByteBuddy is locating the class files and caching the types. + * Possibly used in Quarkus in a future version. + * @param enhancementContext + * @param classLocator + * @return + */ + public @Nullable Enhancer getEnhancer(EnhancementContext enhancementContext, EnhancerClassLocator classLocator) { + return new EnhancerImpl( enhancementContext, byteBuddyState, classLocator ); + } + @Override public void resetCaches() { byteBuddyState.clearState();