Skip to content

Commit

Permalink
HHH-16012 - Develop an abstraction for domain model Class refs
Browse files Browse the repository at this point in the history
- temp ClassLoader work
  • Loading branch information
sebersole committed Apr 4, 2023
1 parent 3c6f704 commit fbdb8ba
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.hibernate.boot.models.source.spi.AnnotationUsage;
import org.hibernate.boot.models.source.spi.ClassDetailsRegistry;
import org.hibernate.boot.models.spi.ModelProcessingContext;
import org.hibernate.boot.spi.ClassLoaderAccess;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.util.collections.CollectionHelper;

Expand All @@ -50,27 +51,22 @@
* @author Steve Ebersole
*/
public class ModelProcessingContextImpl implements ModelProcessingContext {
private final MetadataBuildingContext buildingContext;
private final ClassLoaderAccess classLoaderAccess;
private final AnnotationDescriptorRegistryImpl descriptorRegistry;
private final ClassDetailsRegistry classDetailsRegistry;
private final MetadataBuildingContext buildingContext;

private final Map<AnnotationDescriptor<?>,List<AnnotationUsage<?>>> annotationUsageMap = new HashMap<>();

public ModelProcessingContextImpl(MetadataBuildingContext buildingContext) {
this.buildingContext = buildingContext;
this.descriptorRegistry = new AnnotationDescriptorRegistryImpl( this );
this.classDetailsRegistry = new ClassDetailsRegistryImpl( this );

primeRegistries();
this( buildingContext, buildingContext.getBootstrapContext().getClassLoaderAccess() );
}

public ModelProcessingContextImpl(
ClassDetailsRegistry classDetailsRegistry,
AnnotationDescriptorRegistryImpl annotationDescriptorRegistry,
MetadataBuildingContext buildingContext) {
public ModelProcessingContextImpl(MetadataBuildingContext buildingContext, ClassLoaderAccess classLoaderAccess) {
this.buildingContext = buildingContext;
this.descriptorRegistry = annotationDescriptorRegistry;
this.classDetailsRegistry = classDetailsRegistry;
this.classLoaderAccess = classLoaderAccess;
this.descriptorRegistry = new AnnotationDescriptorRegistryImpl( this );
this.classDetailsRegistry = new ClassDetailsRegistryImpl( this );

primeRegistries();
}
Expand Down Expand Up @@ -142,6 +138,11 @@ public ClassDetailsRegistry getClassDetailsRegistry() {
return classDetailsRegistry;
}

@Override
public ClassLoaderAccess getClassLoaderAccess() {
return classLoaderAccess;
}

@Override
public MetadataBuildingContext getMetadataBuildingContext() {
return buildingContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,27 @@
import org.hibernate.boot.models.source.spi.ClassDetails;
import org.hibernate.boot.models.source.spi.ClassDetailsBuilder;
import org.hibernate.boot.models.spi.ModelProcessingContext;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.spi.ClassLoaderAccess;

/**
* HCANN based ClassDetailsBuilder
*
* @author Steve Ebersole
*/
public class ClassDetailsBuilderImpl implements ClassDetailsBuilder {
private final ClassLoaderService classLoaderService;
private final ClassLoaderAccess classLoaderAccess;
private final ReflectionManager hcannReflectionManager;

public ClassDetailsBuilderImpl(ModelProcessingContext processingContext) {
this.classLoaderService = processingContext.getMetadataBuildingContext()
.getBootstrapContext()
.getServiceRegistry()
.getService( ClassLoaderService.class );
this.classLoaderAccess = processingContext.getClassLoaderAccess();
this.hcannReflectionManager = processingContext.getMetadataBuildingContext()
.getBootstrapContext()
.getReflectionManager();
}

@Override
public ClassDetails buildClassDetails(String name, ModelProcessingContext processingContext) {
final Class<?> classForName = classLoaderService.classForName( name );
final Class<?> classForName = classLoaderAccess.classForName( name );
final XClass xClassForName = hcannReflectionManager.toXClass( classForName );
return new ClassDetailsImpl( xClassForName, processingContext );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ public static ClassDetails buildClassDetailsStatic(String name, ModelProcessingC
return buildClassDetails(
processingContext.getMetadataBuildingContext()
.getBootstrapContext()
.getServiceRegistry()
.getService( ClassLoaderService.class )
.getClassLoaderAccess()
.classForName( name ),
processingContext
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.hibernate.boot.models.source.spi.AnnotationDescriptorRegistry;
import org.hibernate.boot.models.source.spi.AnnotationUsage;
import org.hibernate.boot.models.source.spi.ClassDetailsRegistry;
import org.hibernate.boot.spi.ClassLoaderAccess;
import org.hibernate.boot.spi.MetadataBuildingContext;

/**
Expand All @@ -38,6 +39,8 @@ public interface ModelProcessingContext {
*/
ClassDetailsRegistry getClassDetailsRegistry();

ClassLoaderAccess getClassLoaderAccess();

void registerUsage(AnnotationUsage<?> usage);

<A extends Annotation> List<AnnotationUsage<A>> getAllUsages(AnnotationDescriptor<A> annotationDescriptor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@

import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.java.JavaReflectionManager;
import org.hibernate.boot.internal.ClassLoaderAccessImpl;
import org.hibernate.boot.models.intermediate.internal.EntityHierarchyBuilder;
import org.hibernate.boot.models.intermediate.spi.EntityHierarchy;
import org.hibernate.boot.models.source.internal.ModelProcessingContextImpl;
import org.hibernate.boot.models.source.internal.hcann.ClassDetailsImpl;
import org.hibernate.boot.models.spi.ModelProcessingContext;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.spi.ClassLoaderAccess;

import org.hibernate.testing.boot.ClassLoaderAccessTestingImpl;
import org.hibernate.testing.boot.MetadataBuildingContextTestingImpl;

/**
Expand All @@ -28,6 +31,11 @@ public static ModelProcessingContext buildProcessingContext(StandardServiceRegis
return new ModelProcessingContextImpl( buildingContext );
}

public static ModelProcessingContext buildProcessingContext(StandardServiceRegistry registry, ClassLoaderAccess classLoaderAccess) {
final MetadataBuildingContextTestingImpl buildingContext = new MetadataBuildingContextTestingImpl( registry );
return new ModelProcessingContextImpl( buildingContext, classLoaderAccess );
}

public static Set<EntityHierarchy> buildHierarchies(StandardServiceRegistry registry, Class<?>... classes) {
return buildHierarchies( buildProcessingContext( registry ), classes );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 http://www.gnu.org/licenses/lgpl-2.1.html.
*/

package org.hibernate.orm.test.boot.models.source;

import org.hibernate.boot.models.source.internal.hcann.ClassDetailsImpl;
import org.hibernate.boot.models.source.spi.ClassDetails;
import org.hibernate.boot.models.source.spi.ClassDetailsRegistry;
import org.hibernate.boot.models.spi.ModelProcessingContext;
import org.hibernate.boot.registry.BootstrapServiceRegistry;
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.orm.test.boot.models.ModelHelper;
import org.hibernate.orm.test.util.ClassLoaderAccessImpl;
import org.hibernate.orm.test.util.CollectingClassLoaderService;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Assert expectations about {@link Class} references and how they got loaded
*
* @author Steve Ebersole
*/
public class ClassLoaderAccessTests {
public static final String ENTITY_CLASS_NAME = "org.hibernate.orm.test.boot.models.source.TestEntity";

@Test
public void testClassLoaderIsolation() {
final CollectingClassLoaderService classLoaderService = new CollectingClassLoaderService();
final BootstrapServiceRegistry bootstrapRegistry = new BootstrapServiceRegistryBuilder()
.applyClassLoaderService( classLoaderService )
.build();
final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder( bootstrapRegistry ).build();

final ModelProcessingContext processingContext = ModelHelper.buildProcessingContext( serviceRegistry );
final ClassDetailsRegistry classDetailsRegistry = processingContext.getClassDetailsRegistry();

final ClassDetails classDetails = classDetailsRegistry.resolveClassDetails( ENTITY_CLASS_NAME );
// assert it was handled via the HCANN builder
assertThat( classDetails ).isInstanceOf( ClassDetailsImpl.class );

// Currently ORM completely ignores the temp classloader. That manifests here as the entity class being loaded on the "live" class-loader
assertThat( classLoaderService.getCachedClassForName( ENTITY_CLASS_NAME ) ).isNotNull();
}

@Test
public void testClassLoaderIsolation2() {
// Unlike `#testClassLoaderIsolation`, here we use a ClassLoaderAccess which incorporates the temp ClassLoader.

final CollectingClassLoaderService classLoaderService = new CollectingClassLoaderService();
final BootstrapServiceRegistry bootstrapRegistry = new BootstrapServiceRegistryBuilder()
.applyClassLoaderService( classLoaderService )
.build();
final StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder( bootstrapRegistry ).build();

final ClassLoader tmpClassLoader = new ClassLoader() {};
final ClassLoaderAccessImpl classLoaderAccess = new ClassLoaderAccessImpl( tmpClassLoader, classLoaderService );

final ModelProcessingContext processingContext = ModelHelper.buildProcessingContext( serviceRegistry, classLoaderAccess );
final ClassDetailsRegistry classDetailsRegistry = processingContext.getClassDetailsRegistry();

final ClassDetails classDetails = classDetailsRegistry.resolveClassDetails( ENTITY_CLASS_NAME );
// assert it was handled via the HCANN builder
assertThat( classDetails ).isInstanceOf( ClassDetailsImpl.class );

// Now, the entity is not loaded into the "live" class-loader
assertThat( classLoaderService.getCachedClassForName( ENTITY_CLASS_NAME ) ).isNull();
assertThat( classLoaderAccess.getClassesLoadedFromTempClassLoader() ).containsOnly( ENTITY_CLASS_NAME );
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 http://www.gnu.org/licenses/lgpl-2.1.html.
*/

package org.hibernate.orm.test.boot.models.source;

import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

/**
* @author Steve Ebersole
*/
@Entity
public class TestEntity {
@Id
private Integer id;
@Basic
private String name;

private TestEntity() {
// for use by Hibernate
}

public TestEntity(Integer id, String name) {
this.id = id;
this.name = name;
}

public Integer getId() {
return id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
100 changes: 100 additions & 0 deletions src/test/java/org/hibernate/orm/test/util/ClassLoaderAccessImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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 http://www.gnu.org/licenses/lgpl-2.1.html.
*/
package org.hibernate.orm.test.util;

import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.boot.spi.ClassLoaderAccess;

import org.jboss.logging.Logger;

/**
* Mostly a copy of {@link org.hibernate.boot.internal.ClassLoaderAccessImpl}, just to
* "override" {@link #isSafeClass(String)}
*
* @author Steve Ebersole
*/
public class ClassLoaderAccessImpl implements ClassLoaderAccess {
private static final Logger log = Logger.getLogger( ClassLoaderAccessImpl.class );

private final ClassLoaderService classLoaderService;
private final ClassLoader tempClassLoader;

private final Set<String> classesLoadedFromTempClassLoader;

public ClassLoaderAccessImpl(ClassLoader tempClassLoader, ClassLoaderService classLoaderService) {
this.tempClassLoader = tempClassLoader;
this.classLoaderService = classLoaderService;

this.classesLoadedFromTempClassLoader = tempClassLoader == null
? Collections.emptySet()
: new HashSet<>();
}

@Override
@SuppressWarnings("unchecked")
public Class<?> classForName(String name) {
if ( name == null ) {
throw new IllegalArgumentException( "Name of class to load cannot be null" );
}

if ( isSafeClass( name ) ) {
return classLoaderService.classForName( name );
}
else {
log.debugf( "Not known whether passed class name [%s] is safe", name );
if ( tempClassLoader == null ) {
log.debugf(
"No temp ClassLoader provided; using live ClassLoader " +
"for loading potentially unsafe class : %s",
name
);
return classLoaderService.classForName( name );
}
else {
log.debugf(
"Temp ClassLoader was provided, so we will use that : %s",
name
);
try {
classesLoadedFromTempClassLoader.add( name );
return tempClassLoader.loadClass( name );
}
catch (ClassNotFoundException e) {
throw new ClassLoadingException( name );
}
}
}
}

private boolean isSafeClass(String name) {
// classes in any of these packages are safe to load through the "live" ClassLoader
return name.startsWith( "java." )
|| name.startsWith( "javax." )
|| name.startsWith( "jakarta." )
|| ( name.startsWith( "org.hibernate." ) && !name.contains( ".test." ) );

}

public ClassLoader getTempClassLoader() {
return tempClassLoader;
}

public Set<String> getClassesLoadedFromTempClassLoader() {
return classesLoadedFromTempClassLoader;
}

@Override
public URL locateResource(String resourceName) {
return classLoaderService.locateResource( resourceName );
}
}
Loading

0 comments on commit fbdb8ba

Please sign in to comment.