Skip to content

Commit

Permalink
Take entity class transformations into account during proxy pre-gener…
Browse files Browse the repository at this point in the history
…ation

In particular, don't forget that class transformation can add
getters/setters to entities, and those must be proxied as well.
  • Loading branch information
yrodiere committed Mar 2, 2022
1 parent ab27272 commit b54183d
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem;
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.builditem.TransformedClassesBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
Expand Down Expand Up @@ -143,7 +144,9 @@
import io.quarkus.runtime.configuration.ConfigUtils;
import io.quarkus.runtime.configuration.ConfigurationException;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.pool.TypePool;

/**
* Simulacrum of JPA bootstrap.
Expand Down Expand Up @@ -496,6 +499,7 @@ public void defineJpaEntities(
public ProxyDefinitionsBuildItem pregenProxies(
JpaModelBuildItem jpaModel,
JpaModelIndexBuildItem indexBuildItem,
TransformedClassesBuildItem transformedClassesBuildItem,
List<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptorBuildItems,
BuildProducer<GeneratedClassBuildItem> generatedClassBuildItemBuildProducer,
LiveReloadBuildItem liveReloadBuildItem) {
Expand All @@ -508,7 +512,8 @@ public ProxyDefinitionsBuildItem pregenProxies(
managedClassAndPackageNames.addAll(pud.getManagedClassNames());
}
PreGeneratedProxies proxyDefinitions = generatedProxies(managedClassAndPackageNames,
indexBuildItem.getIndex(), generatedClassBuildItemBuildProducer, liveReloadBuildItem);
indexBuildItem.getIndex(), transformedClassesBuildItem,
generatedClassBuildItemBuildProducer, liveReloadBuildItem);
return new ProxyDefinitionsBuildItem(proxyDefinitions);
}

Expand Down Expand Up @@ -708,7 +713,6 @@ public HibernateModelClassCandidatesForFieldAccessBuildItem candidatesForFieldAc
@Record(STATIC_INIT)
public void build(HibernateOrmRecorder recorder, HibernateOrmConfig hibernateOrmConfig,
BuildProducer<JpaModelPersistenceUnitMappingBuildItem> jpaModelPersistenceUnitMapping,
BuildProducer<BeanContainerListenerBuildItem> buildProducer,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans,
List<PersistenceUnitDescriptorBuildItem> descriptors,
JpaModelBuildItem jpaModel) throws Exception {
Expand Down Expand Up @@ -1481,6 +1485,7 @@ private static MultiTenancyStrategy getMultiTenancyStrategy(Optional<String> mul
}

private PreGeneratedProxies generatedProxies(Set<String> managedClassAndPackageNames, IndexView combinedIndex,
TransformedClassesBuildItem transformedClassesBuildItem,
BuildProducer<GeneratedClassBuildItem> generatedClassBuildItemBuildProducer,
LiveReloadBuildItem liveReloadBuildItem) {
ProxyCache proxyCache = liveReloadBuildItem.getContextObject(ProxyCache.class);
Expand All @@ -1505,7 +1510,9 @@ private PreGeneratedProxies generatedProxies(Set<String> managedClassAndPackageN
}
proxyAnnotations.put(i.target().asClass().name().toString(), proxyClass.asClass().name().toString());
}
try (ProxyBuildingHelper proxyHelper = new ProxyBuildingHelper(Thread.currentThread().getContextClassLoader())) {
TypePool transformedClassesTypePool = createTransformedClassesTypePool(transformedClassesBuildItem,
managedClassAndPackageNames);
try (ProxyBuildingHelper proxyHelper = new ProxyBuildingHelper(transformedClassesTypePool)) {
for (String managedClassOrPackageName : managedClassAndPackageNames) {
CachedProxy result;
if (proxyCache.cache.containsKey(managedClassOrPackageName)
Expand Down Expand Up @@ -1552,6 +1559,27 @@ private PreGeneratedProxies generatedProxies(Set<String> managedClassAndPackageN
return preGeneratedProxies;
}

// Creates a TypePool that is aware of class transformations applied to entity classes,
// so that ByteBuddy can take these transformations into account.
// This is especially important when getters/setters are added to entity classes,
// because we want those methods to be overridden in proxies to trigger proxy initialization.
private TypePool createTransformedClassesTypePool(TransformedClassesBuildItem transformedClassesBuildItem,
Set<String> entityClasses) {
Map<String, byte[]> transformedClasses = new HashMap<>();
for (Set<TransformedClassesBuildItem.TransformedClass> transformedClassSet : transformedClassesBuildItem
.getTransformedClassesByJar().values()) {
for (TransformedClassesBuildItem.TransformedClass transformedClass : transformedClassSet) {
String className = transformedClass.getClassName();
if (entityClasses.contains(className)) {
transformedClasses.put(className, transformedClass.getData());
}
}
}
return TypePool.Default.of(new ClassFileLocator.Compound(
new ClassFileLocator.Simple(transformedClasses),
ClassFileLocator.ForClassLoader.of(Thread.currentThread().getContextClassLoader())));
}

private boolean isModified(String entity, Set<String> changedClasses, IndexView index) {
if (changedClasses.contains(entity)) {
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,45 @@
package io.quarkus.hibernate.orm.deployment;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl;
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper;

import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;

/**
* Makes it slightly more readable to interact with the Hibernate
* ByteBuddyProxyHelper, while improving resource handling.
*/
final class ProxyBuildingHelper implements AutoCloseable {

private final ClassLoader contextClassLoader;
private static final ElementMatcher<? super MethodDescription.InDefinedShape> NO_ARG_CONSTRUCTOR = ElementMatchers
.isConstructor().and(ElementMatchers.takesNoArguments());

private final TypePool typePool;
private ByteBuddyProxyHelper byteBuddyProxyHelper;
private BytecodeProviderImpl bytecodeProvider;

public ProxyBuildingHelper(ClassLoader contextClassLoader) {
this.contextClassLoader = contextClassLoader;
public ProxyBuildingHelper(TypePool typePool) {
this.typePool = typePool;
}

public DynamicType.Unloaded<?> buildUnloadedProxy(String mappedClassName, Set<String> interfaceNames) {
final Class[] interfaces = new Class[interfaceNames.size()];
List<TypeDefinition> interfaces = new ArrayList<>();
int i = 0;
for (String name : interfaceNames) {
interfaces[i++] = uninitializedClass(name);
interfaces.add(typePool.describe(name).resolve());
}
final Class<?> mappedClass = uninitializedClass(mappedClassName);
return getByteBuddyProxyHelper().buildUnloadedProxy(mappedClass, interfaces);
return getByteBuddyProxyHelper().buildUnloadedProxy(typePool, typePool.describe(mappedClassName).resolve(), interfaces);
}

private ByteBuddyProxyHelper getByteBuddyProxyHelper() {
Expand All @@ -43,32 +52,17 @@ private ByteBuddyProxyHelper getByteBuddyProxyHelper() {
return this.byteBuddyProxyHelper;
}

private Class<?> uninitializedClass(String entity) {
try {
return Class.forName(entity, false, contextClassLoader);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}

public boolean isProxiable(String managedClassOrPackageName) {
Class<?> mappedClass;
try {
mappedClass = Class.forName(managedClassOrPackageName, false, contextClassLoader);
} catch (ClassNotFoundException e) {
TypePool.Resolution mappedClassResolution = typePool.describe(managedClassOrPackageName);
if (!mappedClassResolution.isResolved()) {
// Probably a package name - consider it's not proxiable.
return false;
}

if (Modifier.isFinal(mappedClass.getModifiers())) {
return false;
}
try {
mappedClass.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
return false;
}
return true;
TypeDescription mappedClass = mappedClassResolution.resolve();

return !mappedClass.isFinal()
&& !mappedClass.getDeclaredMethods().filter(NO_ARG_CONSTRUCTOR).isEmpty();
}

@Override
Expand Down

0 comments on commit b54183d

Please sign in to comment.