Skip to content

Commit

Permalink
HHH-17240 Make ClassTransformer enhancement thread safe
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Sep 22, 2023
1 parent 884e1b1 commit 3345658
Showing 1 changed file with 27 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

import org.hibernate.Version;
import org.hibernate.bytecode.enhance.VersionMismatchException;
Expand Down Expand Up @@ -145,12 +147,14 @@ 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.setClassNameAndBytes( safeClassName, originalBytes );
classFileLocator.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 ) ),
() -> byteBuddy.ignore( isDefaultFinalizer() )
.redefine( typeDescription, ClassFileLocator.Simple.of( safeClassName, originalBytes ) )
.annotateType( HIBERNATE_VERSION_ANNOTATION ),
typeDescription
) );
}
Expand All @@ -160,12 +164,15 @@ public byte[] enhance(String className, byte[] originalBytes) throws Enhancement
catch (RuntimeException e) {
throw new EnhancementException( "Failed to enhance class " + className, e );
}
finally {
classFileLocator.deregisterClassNameAndBytes( safeClassName );
}
}

@Override
public void discoverTypes(String className, byte[] originalBytes) {
if ( originalBytes != null ) {
classFileLocator.setClassNameAndBytes( className, originalBytes );
classFileLocator.registerClassNameAndBytes( className, originalBytes );
}
try {
final TypeDescription typeDescription = typePool.describe( className ).resolve();
Expand All @@ -175,13 +182,16 @@ public void discoverTypes(String className, byte[] originalBytes) {
catch (RuntimeException e) {
throw new EnhancementException( "Failed to discover types for class " + className, e );
}
finally {
classFileLocator.deregisterClassNameAndBytes( className );
}
}

private TypePool buildTypePool(final ClassFileLocator classFileLocator) {
return TypePool.Default.WithLazyResolution.of( classFileLocator );
}

private DynamicType.Builder<?> doEnhance(DynamicType.Builder<?> builder, TypeDescription managedCtClass) {
private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builderSupplier, TypeDescription managedCtClass) {
// can't effectively enhance interfaces
if ( managedCtClass.isInterface() ) {
log.debugf( "Skipping enhancement of [%s]: it's an interface", managedCtClass.getName() );
Expand All @@ -202,10 +212,9 @@ private DynamicType.Builder<?> doEnhance(DynamicType.Builder<?> builder, TypeDes
return null;
}

builder = builder.annotateType( HIBERNATE_VERSION_ANNOTATION );

if ( enhancementContext.isEntityClass( managedCtClass ) ) {
log.debugf( "Enhancing [%s] as Entity", managedCtClass.getName() );
DynamicType.Builder<?> builder = builderSupplier.get();
builder = builder.implement( ManagedEntity.class )
.defineMethod( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME, Object.class, Visibility.PUBLIC )
.intercept( FixedValue.self() );
Expand Down Expand Up @@ -357,6 +366,7 @@ private DynamicType.Builder<?> doEnhance(DynamicType.Builder<?> builder, TypeDes
else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
log.debugf( "Enhancing [%s] as Composite", managedCtClass.getName() );

DynamicType.Builder<?> builder = builderSupplier.get();
builder = builder.implement( ManagedComposite.class );
builder = addInterceptorHandling( builder, managedCtClass );

Expand Down Expand Up @@ -390,12 +400,13 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
log.debugf( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() );

DynamicType.Builder<?> builder = builderSupplier.get();
builder = builder.implement( ManagedMappedSuperclass.class );
return createTransformer( managedCtClass ).applyTo( builder );
}
else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
log.debugf( "Extended enhancement of [%s]", managedCtClass.getName() );
return createTransformer( managedCtClass ).applyExtended( builder );
return createTransformer( managedCtClass ).applyExtended( builderSupplier.get() );
}
else {
log.debugf( "Skipping enhancement of [%s]: not entity or composite", managedCtClass.getName() );
Expand Down Expand Up @@ -634,11 +645,7 @@ else if ( access != null && access.load().value() == AccessType.FIELD ) {
}

private static class EnhancerClassFileLocator extends ClassFileLocator.ForClassLoader {

// The name of the class to (possibly be) transformed.
private String className;
// The explicitly resolved Resolution for the class to (possibly be) transformed.
private Resolution resolution;
private final ConcurrentHashMap<String, Resolution> resolutions = new ConcurrentHashMap<>();

/**
* Creates a new class file locator for the given class loader.
Expand All @@ -652,19 +659,23 @@ protected EnhancerClassFileLocator(ClassLoader classLoader) {
@Override
public Resolution locate(String className) throws IOException {
assert className != null;
if ( className.equals( this.className ) ) {
final Resolution resolution = resolutions.get( className );
if ( resolution != null ) {
return resolution;
}
else {
return super.locate( className );
}
}

void setClassNameAndBytes(String className, byte[] bytes) {
void registerClassNameAndBytes(String className, byte[] bytes) {
assert className != null;
assert bytes != null;
this.className = className;
this.resolution = new Resolution.Explicit( bytes);
resolutions.put( className, new Resolution.Explicit( bytes ) );
}

void deregisterClassNameAndBytes(String className) {
resolutions.remove( className );
}
}

Expand Down

0 comments on commit 3345658

Please sign in to comment.