Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1418,9 +1418,8 @@ private PreGeneratedProxies generateProxies(Set<String> managedClassAndPackageNa
// we now are sure we have a proper class and not a package, let's avoid the confusion
String managedClass = managedClassOrPackageName;
generatedProxyQueue.add(buildExecutor.submit(() -> {
DynamicType.Unloaded<?> unloaded = proxyHelper.buildUnloadedProxy(managedClass,
proxyInterfaceNames);
return new CachedProxy(managedClass, unloaded, proxyInterfaceNames);
DynamicType.Unloaded<?> unloaded = proxyHelper.buildUnloadedProxy(managedClass);
return new CachedProxy(managedClass, unloaded);
}));
}
}
Expand All @@ -1433,7 +1432,7 @@ private PreGeneratedProxies generateProxies(Set<String> managedClassAndPackageNa
.produce(new GeneratedClassBuildItem(true, i.getKey().getName(), i.getValue()));
}
preGeneratedProxies.getProxies().put(proxy.managedClassName, new PreGeneratedProxies.ProxyClassDetailsHolder(
proxy.proxyDef.getTypeDescription().getName(), proxy.interfaces));
proxy.proxyDef.getTypeDescription().getName()));
}
}

Expand Down Expand Up @@ -1496,12 +1495,10 @@ private static final class ProxyCache {
static final class CachedProxy {
final String managedClassName;
final DynamicType.Unloaded<?> proxyDef;
final Set<String> interfaces;

CachedProxy(String managedClassName, DynamicType.Unloaded<?> proxyDef, Set<String> interfaces) {
CachedProxy(String managedClassName, DynamicType.Unloaded<?> proxyDef) {
this.managedClassName = managedClassName;
this.proxyDef = proxyDef;
this.interfaces = interfaces;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
package io.quarkus.hibernate.orm.deployment;

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 org.jboss.jandex.ClassInfo;

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

/**
Expand All @@ -22,23 +17,17 @@
*/
final class ProxyBuildingHelper implements AutoCloseable {

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

private final TypePool typePool;
private final List<TypeDefinition> interfaces;
private ByteBuddyProxyHelper byteBuddyProxyHelper;
private BytecodeProviderImpl bytecodeProvider;

public ProxyBuildingHelper(TypePool typePool) {
this.typePool = typePool;
this.interfaces = List.of(typePool.describe(ClassNames.HIBERNATE_PROXY.toString()).resolve());
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl;
import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl;
import org.hibernate.bytecode.internal.ProxyFactoryFactoryInitiator;
import org.hibernate.engine.config.internal.ConfigurationServiceInitiator;
import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator;
import org.hibernate.engine.jdbc.connections.internal.MultiTenantConnectionProviderInitiator;
Expand All @@ -37,7 +38,6 @@
import io.quarkus.hibernate.orm.runtime.customized.QuarkusJndiServiceInitiator;
import io.quarkus.hibernate.orm.runtime.customized.QuarkusJtaPlatformInitiator;
import io.quarkus.hibernate.orm.runtime.customized.QuarkusRuntimeProxyFactoryFactory;
import io.quarkus.hibernate.orm.runtime.customized.QuarkusRuntimeProxyFactoryFactoryInitiator;
import io.quarkus.hibernate.orm.runtime.customized.QuarkusStrategySelectorBuilder;
import io.quarkus.hibernate.orm.runtime.recording.RecordedState;
import io.quarkus.hibernate.orm.runtime.service.CfgXmlAccessServiceInitiatorQuarkus;
Expand Down Expand Up @@ -160,18 +160,17 @@ private static List<StandardServiceInitiator<?>> buildQuarkusServiceInitiatorLis

//References to this object need to be injected in both the initiator for BytecodeProvider and for
//the registered ProxyFactoryFactoryInitiator
QuarkusRuntimeProxyFactoryFactory statefulProxyFactory = new QuarkusRuntimeProxyFactoryFactory(
QuarkusRuntimeProxyFactoryFactory preGeneratedProxyFactory = new QuarkusRuntimeProxyFactoryFactory(
rs.getProxyClassDefinitions());

//Enforces no bytecode enhancement will happen at runtime,
//but allows use of proxies generated at build time
serviceInitiators.add(new QuarkusRuntimeBytecodeProviderInitiator(statefulProxyFactory));
serviceInitiators.add(new QuarkusRuntimeBytecodeProviderInitiator(preGeneratedProxyFactory));

//Routes to the standard implementation, but w/o allowing configuration options to override it
serviceInitiators.add(QuarkusMutationExecutorServiceInitiator.INSTANCE);

//Use a custom ProxyFactoryFactory which is able to use the class definitions we already created:
serviceInitiators.add(new QuarkusRuntimeProxyFactoryFactoryInitiator(statefulProxyFactory));
serviceInitiators.add(ProxyFactoryFactoryInitiator.INSTANCE);

// Replaces org.hibernate.boot.cfgxml.internal.CfgXmlAccessServiceInitiator :
// not used
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Locale;
import java.util.Set;

import org.hibernate.HibernateException;
Expand Down Expand Up @@ -57,8 +59,31 @@ public void postInstantiate(String entityName, Class<?> persistentClass, Set<Cla
this.componentIdType = componentIdType;
ProxyDefinitions.ProxyClassDetailsHolder detailsHolder = proxyClassDefinitions.getProxyForClass(persistentClass);
if (detailsHolder == null) {
throw new HibernateException("Could not lookup a pre-generated proxy class definition for entity '" + entityName
+ "'. Falling back to enforced eager mode for this entity!");
String reason = null;
// Some Envers entity classes are final, e.g. org.hibernate.envers.DefaultRevisionEntity
// There's nothing users can do about it, so let's not fail in those cases.
if (persistentClass.getName().startsWith("org.hibernate.")) {
reason = "this is a limitation of this particular Hibernate class.";
}
// See also ProxyBuildingHelper#isProxiable
else if (Modifier.isFinal(persistentClass.getModifiers())) {
reason = "this class is final. Your application might perform better if this class was non-final.";
}
if (Modifier.isAbstract(persistentClass.getModifiers())) {
reason = "this class is abstract. Your application might perform better if this class was non-abstract.";
}
if (reason != null) {
// This is caught and logged as a warning by Hibernate ORM.
throw new HibernateException(String.format(Locale.ROOT,
"Could not lookup a pre-generated proxy class definition for entity '%s' (class='%s'): %s", entityName,
persistentClass.getCanonicalName(), reason));
} else {
// This will fail bootstrap.
throw new IllegalStateException(String.format(Locale.ROOT,
"Could not lookup a pre-generated proxy class definition for entity '%s' (class='%s')." +
"This should not happen, please open an issue at https://github.com/quarkusio/quarkus/issues",
entityName, persistentClass.getCanonicalName()));
}
}
this.overridesEquals = detailsHolder.isOverridesEquals();
this.constructor = detailsHolder.getConstructor();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package io.quarkus.hibernate.orm.runtime.proxies;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* A holder class for proxies that were generated at build time,
Expand Down Expand Up @@ -31,15 +29,13 @@ public void setProxies(Map<String, ProxyClassDetailsHolder> proxies) {
public static class ProxyClassDetailsHolder {

private String className;
private Set<String> proxyInterfaces = new HashSet<>();

public ProxyClassDetailsHolder() {

}

public ProxyClassDetailsHolder(String className, Set<String> proxyInterfaces) {
public ProxyClassDetailsHolder(String className) {
this.className = className;
this.proxyInterfaces = proxyInterfaces;
}

public String getClassName() {
Expand All @@ -50,13 +46,5 @@ public void setClassName(String className) {
this.className = className;
}

public Set<String> getProxyInterfaces() {
return proxyInterfaces;
}

public void setProxyInterfaces(Set<String> proxyInterfaces) {
this.proxyInterfaces = proxyInterfaces;
}

}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
package io.quarkus.hibernate.orm.runtime.proxies;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

import org.hibernate.HibernateException;
import org.hibernate.boot.Metadata;
import org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper;
import org.jboss.logging.Logger;

import net.bytebuddy.ClassFileVersion;

/**
* Runtime proxies are used by Hibernate ORM to handle a number of corner cases;
Expand All @@ -38,44 +31,36 @@
* will have us abort the bootstrap process with a critical error.
* On the other hand, having the entities marked as "final" is handled gracefully, as we
* can simply fall back to not use the enhanced proxy for the specific entity, and because
* it's a common case when writing entities in Kotlin.
* it's a common case when writing entities in Kotlin. "abstract" entity types are handled similarly.
*/
public final class ProxyDefinitions {

private final Map<Class<?>, ProxyClassDetailsHolder> proxyDefinitionMap;
private static final Logger LOGGER = Logger.getLogger(ProxyDefinitions.class.getName());

private ProxyDefinitions(Map<Class<?>, ProxyClassDetailsHolder> proxyDefinitionMap) {
this.proxyDefinitionMap = proxyDefinitionMap;
}

public static ProxyDefinitions createFromMetadata(Metadata storeableMetadata, PreGeneratedProxies preGeneratedProxies) {
//Check upfront for any need across all metadata: would be nice to avoid initializing the Bytecode provider.
LazyBytecode lazyBytecode = new LazyBytecode();
if (needAnyProxyDefinitions(storeableMetadata)) {
final HashMap<Class<?>, ProxyClassDetailsHolder> proxyDefinitionMap = new HashMap<>();
try {
for (PersistentClass persistentClass : storeableMetadata.getEntityBindings()) {
if (needsProxyGeneration(persistentClass)) {
final Class mappedClass = persistentClass.getMappedClass();
final Class proxyClassDefinition = generateProxyClass(persistentClass, lazyBytecode,
preGeneratedProxies);
if (proxyClassDefinition == null) {
continue;
}
final boolean overridesEquals = ReflectHelper.overridesEquals(mappedClass);
try {
proxyDefinitionMap.put(mappedClass,
new ProxyClassDetailsHolder(overridesEquals, proxyClassDefinition.getConstructor()));
} catch (NoSuchMethodException e) {
throw new HibernateException(
"Failed to generate Enhanced Proxy: default constructor is missing for entity '"
+ mappedClass.getName() + "'. Please add a default constructor explicitly.");
}
for (PersistentClass persistentClass : storeableMetadata.getEntityBindings()) {
if (needsProxyGeneration(persistentClass)) {
final Class<?> mappedClass = persistentClass.getMappedClass();
final Class<?> proxyClassDefinition = getProxyClass(persistentClass, preGeneratedProxies);
if (proxyClassDefinition == null) {
continue;
}
final boolean overridesEquals = ReflectHelper.overridesEquals(mappedClass);
try {
proxyDefinitionMap.put(mappedClass,
new ProxyClassDetailsHolder(overridesEquals, proxyClassDefinition.getConstructor()));
} catch (NoSuchMethodException e) {
throw new HibernateException(
"Failed to generate Enhanced Proxy: default constructor is missing for entity '"
+ mappedClass.getName() + "'. Please add a default constructor explicitly.");
}
}
} finally {
lazyBytecode.close();
}
return new ProxyDefinitions(proxyDefinitionMap);
} else {
Expand All @@ -96,65 +81,22 @@ private static boolean needsProxyGeneration(PersistentClass persistentClass) {
return persistentClass.isLazy() && (persistentClass.getMappedClass() != null);
}

private static Class<?> generateProxyClass(PersistentClass persistentClass,
Supplier<ByteBuddyProxyHelper> byteBuddyProxyHelper,
private static Class<?> getProxyClass(PersistentClass persistentClass,
PreGeneratedProxies preGeneratedProxies) {
final String entityName = persistentClass.getEntityName();
final Class mappedClass = persistentClass.getMappedClass();
if (Modifier.isFinal(mappedClass.getModifiers())) {
// Some Envers entity classes are final, e.g. org.hibernate.envers.DefaultRevisionEntity
// There's nothing users can do about it, so let's not warn in those cases.
if (!mappedClass.getName().startsWith("org.hibernate.")) {
LOGGER.warn("Could not generate an enhanced proxy for entity '" + entityName + "' (class='"
+ mappedClass.getCanonicalName()
+ "') as it's final. Your application might perform better if we're allowed to extend it.");
}
return null;
}
final java.util.Set<Class<?>> proxyInterfaces = org.hibernate.proxy.pojo.ProxyFactoryHelper
.extractProxyInterfaces(persistentClass, entityName);
PreGeneratedProxies.ProxyClassDetailsHolder preProxy = preGeneratedProxies.getProxies()
.get(persistentClass.getClassName());
Class<?> preGeneratedProxy = null;
boolean match = true;
if (preProxy != null) {
match = proxyInterfaces.size() == preProxy.getProxyInterfaces().size();
if (match) {
for (Class i : proxyInterfaces) {
if (!preProxy.getProxyInterfaces().contains(i.getName())) {
match = false;
break;
}
}
}
if (match) {
try {
preGeneratedProxy = Class.forName(preProxy.getClassName(), false,
Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
//should never happen
throw new RuntimeException("Unable to load proxy class", e);
}
}
if (preProxy == null) {
// Behavior determined at runtime: will either log a warning and not use a proxy, or fail bootstrap.
// See QuarkusProxyFactory for details.
return null;
}

if (preGeneratedProxy == null) {
if (match) {
LOGGER.warnf("Unable to find a build time generated proxy for entity %s",
persistentClass.getClassName());
} else {
//TODO: this should be changed to an exception after 1.4
//really it should be an exception now
LOGGER.errorf(
"Unable to use a build time generated proxy for entity %s, as the build time proxy " +
"interfaces %s are different to the runtime ones %s. This should not happen, please open an " +
"issue at https://github.com/quarkusio/quarkus/issues",
persistentClass.getClassName(), preProxy.getProxyInterfaces(), proxyInterfaces);
}
Class<?> proxyDef = byteBuddyProxyHelper.get().buildProxy(mappedClass, toArray(proxyInterfaces));
return proxyDef;
} else {
return preGeneratedProxy;
try {
return Class.forName(preProxy.getClassName(), false,
Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
//should never happen
throw new RuntimeException("Unable to load proxy class", e);
}
}

Expand Down Expand Up @@ -188,25 +130,4 @@ public Constructor getConstructor() {
}
}

private static final class LazyBytecode implements Supplier<ByteBuddyProxyHelper> {

ByteBuddyProxyHelper helper;
private BytecodeProviderImpl bytecodeProvider;

@Override
public ByteBuddyProxyHelper get() {
if (helper == null) {
bytecodeProvider = new BytecodeProviderImpl(ClassFileVersion.JAVA_V11);
helper = bytecodeProvider.getByteBuddyProxyHelper();
}
return helper;
}

void close() {
if (bytecodeProvider != null) {
bytecodeProvider.resetCaches();
}
}
}

}
Loading
Loading