Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Take entity class transformations into account during Hibernate proxy pre-generation #24059

Merged
Merged
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
@@ -914,7 +914,7 @@ public void execute(final BuildContext bc) {
return new RuntimeValue<>(object);
}
}
throw new RuntimeException("Cannot inject type " + s);
return null;
})
: null;
for (int i = 0; i < methodArgs.length; i++) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.quarkus.deployment.builditem;

import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.deployment.recording.BytecodeRecorderImpl;

/**
* The definition of a constant
* that can be injected into recorders via their {@code @Inject}-annotated constructor.
*
* Compared to simply passing the value to a recorder proxy,
* this build item allows for injecting values into recorders
* without introducing new dependencies from build steps
* that use the recorder to build steps that create the constant value.
* This can be useful in complex dependency graphs.
*/
public final class BytecodeRecorderConstantDefinitionBuildItem extends MultiBuildItem {

private final Holder<?> holder;

public <T> BytecodeRecorderConstantDefinitionBuildItem(Class<T> type, T value) {
this.holder = new Holder<>(type, value);
}

public void register(BytecodeRecorderImpl recorder) {
holder.register(recorder);
}

// Necessary because generics are not allowed on BuildItems.
private static class Holder<T> {
private final Class<T> type;
private final T value;

public Holder(Class<T> type, T value) {
this.type = type;
this.value = value;
}

public void register(BytecodeRecorderImpl recorder) {
recorder.registerConstant(type, value);
}
}
}
Original file line number Diff line number Diff line change
@@ -19,8 +19,6 @@
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.ConfigRoot;

/**
* A factory that can generate proxies of a class.
@@ -55,7 +53,7 @@ public ProxyFactory(ProxyConfiguration<T> configuration) {
if (!findConstructor(superClass, configuration.isAllowPackagePrivate(), true)) {
throw new IllegalArgumentException(
"A proxy cannot be created for class " + this.superClassName
+ " because it does contain a no-arg constructor");
+ " because it does not declare a no-arg constructor");
}
if (Modifier.isFinal(superClass.getModifiers())) {
throw new IllegalArgumentException(
@@ -88,20 +86,9 @@ private boolean findConstructor(Class<?> clazz, boolean allowPackagePrivate, boo
//ctor needs to be @Inject or the only constructor
if (constructor.isAnnotationPresent(Inject.class)
|| (ctors.length == 1 && constructor.getParameterCount() > 0)) {
if (!isModifiedCorrect(allowPackagePrivate, constructor)) {
if (!isModifierCorrect(allowPackagePrivate, constructor)) {
return false;
}
//if we have a constructor with only simple arguments (i.e. that also have a no-arg constructor)
//then we will use that, and just create the types
//this allows us to create proxys for recorders that use constructor injection for config objects
for (var i : constructor.getParameterTypes()) {
if (!(i.isAnnotationPresent(ConfigRoot.class) || i == RuntimeValue.class)) {
return false;
}
if (!findConstructor(i, allowPackagePrivate, false)) {
return false;
}
}
injectConstructor = constructor;
return true;
}
@@ -110,13 +97,13 @@ private boolean findConstructor(Class<?> clazz, boolean allowPackagePrivate, boo
for (Constructor<?> constructor : ctors) {
if (constructor.getParameterCount() == 0) {
injectConstructor = constructor;
return isModifiedCorrect(allowPackagePrivate, constructor);
return isModifierCorrect(allowPackagePrivate, constructor);
}
}
return false;
}

private boolean isModifiedCorrect(boolean allowPackagePrivate, Constructor<?> constructor) {
private boolean isModifierCorrect(boolean allowPackagePrivate, Constructor<?> constructor) {
if (allowPackagePrivate) {
return !Modifier.isPrivate(constructor.getModifiers());
}
@@ -271,9 +258,18 @@ public T newInstance(InvocationHandler handler) throws IllegalAccessException, I
args[0] = handler;
Class<?>[] parameterTypes = this.constructor.getParameterTypes();
for (int i = 1; i < constructor.getParameterCount(); ++i) {
Constructor<?> ctor = parameterTypes[i].getConstructor();
ctor.setAccessible(true);
args[i] = ctor.newInstance();
Constructor<?> paramConstructor = null;
try {
paramConstructor = parameterTypes[i].getConstructor();
} catch (NoSuchMethodException e) {
// We won't use the constructor
}
if (paramConstructor != null) {
paramConstructor.setAccessible(true);
args[i] = paramConstructor.newInstance();
} else {
args[i] = null;
}
}
return (T) constructor.newInstance(args);
} catch (Exception e) {
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
@@ -130,6 +131,7 @@ public class BytecodeRecorderImpl implements RecorderContext {
private final Function<java.lang.reflect.Type, Object> configCreatorFunction;

private final List<ObjectLoader> loaders = new ArrayList<>();
private final Map<Class<?>, ConstantHolder<?>> constants = new HashMap<>();
private final Set<Class> classesToUseRecorableConstructor = new HashSet<>();
private final boolean useIdentityComparison;

@@ -239,6 +241,12 @@ public void registerObjectLoader(ObjectLoader loader) {
loaders.add(loader);
}

public <T> void registerConstant(Class<T> type, T value) {
Assert.checkNotNullParam("type", type);
Assert.checkNotNullParam("value", value);
constants.put(type, new ConstantHolder<>(type, value));
}

@Override
public Class<?> classProxy(String name) {
// if it's a primitive there is no need to create a proxy (and doing so would result in errors when the value is used)
@@ -512,7 +520,7 @@ ResultHandle createValue(MethodContext context, MethodCreator method, ResultHand
}
}
for (var e : existingRecorderValues.entrySet()) {
e.getValue().preWrite();
e.getValue().preWrite(parameterMap);
}

//when this is true it is no longer possible to allocate items in the array. this is a guard against programmer error
@@ -1555,6 +1563,19 @@ ResultHandle createValue(MethodContext context, MethodCreator method, ResultHand
return null;
}

private ConstantHolder<?> findConstantForParam(final java.lang.reflect.Type paramType) {
ConstantHolder<?> holder = null;
if (paramType instanceof Class) {
holder = constants.get(paramType);
} else if (paramType instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) paramType;
if (p.getRawType() == RuntimeValue.class) {
holder = constants.get(p.getActualTypeArguments()[0]);
}
}
return holder;
}

interface BytecodeInstruction {

}
@@ -1623,11 +1644,25 @@ final class NewRecorder extends DeferredArrayStoreParameter {
this.injectCtor = injectCtor;
}

void preWrite() {
void preWrite(Map<Object, DeferredParameter> parameterMap) {
if (injectCtor != null) {
try {
for (java.lang.reflect.Type param : injectCtor.getGenericParameterTypes()) {
java.lang.reflect.Type[] parameterTypes = injectCtor.getGenericParameterTypes();
Annotation[][] parameterAnnotations = injectCtor.getParameterAnnotations();
for (int i = 0; i < parameterTypes.length; i++) {
java.lang.reflect.Type param = parameterTypes[i];
var constantHolder = findConstantForParam(param);
if (constantHolder != null) {
deferredParameters.add(loadObjectInstance(constantHolder.value, parameterMap,
constantHolder.type, Arrays.stream(parameterAnnotations[i])
.anyMatch(s -> s.annotationType() == RelaxedValidation.class)));
continue;
}
var obj = configCreatorFunction.apply(param);
if (obj == null) {
// No matching constant nor config.
throw new RuntimeException("Cannot inject type " + param);
}
if (obj instanceof RuntimeValue) {
if (!staticInit) {
var result = findLoaded(((RuntimeValue<?>) obj).getValue());
@@ -1720,6 +1755,16 @@ static final class NonDefaultConstructorHolder {
}
}

static final class ConstantHolder<T> {
final Class<T> type;
final T value;

ConstantHolder(Class<T> type, T value) {
this.type = type;
this.value = value;
}
}

private static final class ProxyInstance {
final Object proxy;
final String key;
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@
import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem;
import io.quarkus.deployment.builditem.ApplicationClassNameBuildItem;
import io.quarkus.deployment.builditem.ApplicationInfoBuildItem;
import io.quarkus.deployment.builditem.BytecodeRecorderConstantDefinitionBuildItem;
import io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
@@ -107,6 +108,7 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
List<FeatureBuildItem> features,
BuildProducer<ApplicationClassNameBuildItem> appClassNameProducer,
List<BytecodeRecorderObjectLoaderBuildItem> loaders,
List<BytecodeRecorderConstantDefinitionBuildItem> constants,
List<RecordableConstructorBuildItem> recordableConstructorBuildItems,
BuildProducer<GeneratedClassBuildItem> generatedClass,
LaunchModeBuildItem launchMode,
@@ -173,8 +175,7 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
tryBlock.invokeStaticMethod(CONFIGURE_STEP_TIME_START);
for (StaticBytecodeRecorderBuildItem holder : staticInitTasks) {
writeRecordedBytecode(holder.getBytecodeRecorder(), null, substitutions, recordableConstructorBuildItems, loaders,
gizmoOutput, startupContext,
tryBlock);
constants, gizmoOutput, startupContext, tryBlock);
}
tryBlock.returnValue(null);

@@ -249,7 +250,7 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
for (MainBytecodeRecorderBuildItem holder : mainMethod) {
writeRecordedBytecode(holder.getBytecodeRecorder(), holder.getGeneratedStartupContextClassName(), substitutions,
recordableConstructorBuildItems,
loaders, gizmoOutput, startupContext, tryBlock);
loaders, constants, gizmoOutput, startupContext, tryBlock);
}

// Startup log messages
@@ -418,7 +419,9 @@ private void generateMainForQuarkusApplication(String quarkusApplicationClassNam
private void writeRecordedBytecode(BytecodeRecorderImpl recorder, String fallbackGeneratedStartupTaskClassName,
List<ObjectSubstitutionBuildItem> substitutions,
List<RecordableConstructorBuildItem> recordableConstructorBuildItems,
List<BytecodeRecorderObjectLoaderBuildItem> loaders, GeneratedClassGizmoAdaptor gizmoOutput,
List<BytecodeRecorderObjectLoaderBuildItem> loaders,
List<BytecodeRecorderConstantDefinitionBuildItem> constants,
GeneratedClassGizmoAdaptor gizmoOutput,
ResultHandle startupContext, BytecodeCreator bytecodeCreator) {

if ((recorder == null || recorder.isEmpty()) && fallbackGeneratedStartupTaskClassName == null) {
@@ -436,6 +439,9 @@ private void writeRecordedBytecode(BytecodeRecorderImpl recorder, String fallbac
for (var item : recordableConstructorBuildItems) {
recorder.markClassAsConstructorRecordable(item.getClazz());
}
for (BytecodeRecorderConstantDefinitionBuildItem constant : constants) {
constant.register(recorder);
}
recorder.writeBytecode(gizmoOutput);
}

Original file line number Diff line number Diff line change
@@ -383,6 +383,28 @@ void runTest(Consumer<BytecodeRecorderImpl> generator, Object... expected) throw
}
}

@Test
public void testConstantInjection() throws Exception {
runTest(generator -> {
generator.registerConstant(TestJavaBean.class, new TestJavaBean("Some string", 42));
TestRecorderWithTestJavaBeanInjectedInConstructor recorder = generator
.getRecordingProxy(TestRecorderWithTestJavaBeanInjectedInConstructor.class);
recorder.retrieveConstant();
}, new TestJavaBean("Some string", 42));
}

@Test
public void testConstantInjectionAndSubstitution() throws Exception {
runTest(generator -> {
generator.registerConstant(NonSerializable.class, new NonSerializable("Some string", 42));
generator.registerSubstitution(NonSerializable.class, NonSerializable.Serialized.class,
NonSerializable.Substitution.class);
TestRecorderWithNonSerializableInjectedInConstructor recorder = generator
.getRecordingProxy(TestRecorderWithNonSerializableInjectedInConstructor.class);
recorder.retrieveConstant();
}, new NonSerializable("Some string", 42));
}

private static class TestClassOutput implements ClassOutput {
private final TestClassLoader tcl;

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.deployment.recording;

import javax.inject.Inject;

public class TestRecorderWithNonSerializableInjectedInConstructor {

private final NonSerializable constant;

@Inject
public TestRecorderWithNonSerializableInjectedInConstructor(NonSerializable constant) {
this.constant = constant;
}

public void retrieveConstant() {
TestRecorder.RESULT.add(constant);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.deployment.recording;

import javax.inject.Inject;

public class TestRecorderWithTestJavaBeanInjectedInConstructor {

private final TestJavaBean constant;

@Inject
public TestRecorderWithTestJavaBeanInjectedInConstructor(TestJavaBean constant) {
this.constant = constant;
}

public void retrieveConstant() {
TestRecorder.RESULT.add(constant);
}

}
Loading