
io.quarkus.hibernate.orm.runtime.proxies.ProxyDefinitions Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of quarkus-hibernate-orm Show documentation
Show all versions of quarkus-hibernate-orm Show documentation
Define your persistent model with Hibernate ORM and JPA
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.ProxyFactoryHelper;
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;
* in particular Enhanced Proxies need special consideration in Quarkus as
* they aren't generated by the enhancers during the build.
* Since we can't generate new class definitions at runtime, this value holder
* class is meant to be created at build time and hold onto those class definitions.
*
* Implementors of a custom {@link org.hibernate.bytecode.spi.ProxyFactoryFactory} are
* then able to look up such class definitions at runtime to create new instances of the
* required enhanced proxies.
*
* Failure to generate such a proxy is not critical, but it implies that Hibernate ORM
* will not be able to use the enhanced proxy mechanism, possibly having to generate
* an additional round trip to the database in some circumstances.
* Most notably we'll fail to generate such a proxy when the entity has a "final" modifier;
* we'll also need a default constructor.
* Default constructors are required beyond proxy generation, so a lack of such a constructor
* 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.
*/
public final class ProxyDefinitions {
private final Map, ProxyClassDetailsHolder> proxyDefinitionMap;
private static final Logger LOGGER = Logger.getLogger(ProxyDefinitions.class.getName());
private ProxyDefinitions(Map, 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, 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.");
}
}
}
} finally {
lazyBytecode.close();
}
return new ProxyDefinitions(proxyDefinitionMap);
} else {
return new ProxyDefinitions(Collections.emptyMap());
}
}
private static boolean needAnyProxyDefinitions(Metadata storeableMetadata) {
for (PersistentClass persistentClass : storeableMetadata.getEntityBindings()) {
if (needsProxyGeneration(persistentClass))
return true;
}
return false;
}
private static boolean needsProxyGeneration(PersistentClass persistentClass) {
//Only lazy entities need a proxy, and only class-mapped classed can be proxies (Envers!)
return persistentClass.isLazy() && (persistentClass.getMappedClass() != null);
}
private static Class> generateProxyClass(PersistentClass persistentClass,
Supplier byteBuddyProxyHelper,
PreGeneratedProxies preGeneratedProxies) {
final String entityName = persistentClass.getEntityName();
final Class mappedClass = persistentClass.getMappedClass();
if (Modifier.isFinal(mappedClass.getModifiers())) {
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> 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 (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;
}
}
private static Class[] toArray(final java.util.Set> interfaces) {
if (interfaces == null) {
return ArrayHelper.EMPTY_CLASS_ARRAY;
}
return interfaces.toArray(new Class[interfaces.size()]);
}
public ProxyClassDetailsHolder getProxyForClass(Class persistentClass) {
return proxyDefinitionMap.get(persistentClass);
}
public static class ProxyClassDetailsHolder {
private final boolean overridesEquals;
private final Constructor constructor;
private ProxyClassDetailsHolder(boolean overridesEquals, Constructor constructor) {
this.overridesEquals = overridesEquals;
this.constructor = constructor;
}
public boolean isOverridesEquals() {
return overridesEquals;
}
public Constructor getConstructor() {
return constructor;
}
}
private static final class LazyBytecode implements Supplier {
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();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy