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

Extend reflection configuration for SmallRye FT #42229

Closed
wants to merge 3 commits into from
Closed
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 @@ -17,6 +17,7 @@ public final class ReflectiveClassBuildItem extends MultiBuildItem {
private final List<String> className;
private final boolean methods;
private final boolean queryMethods;
private final boolean queryPublicMethods;
private final boolean fields;
private final boolean classes;
private final boolean constructors;
Expand Down Expand Up @@ -46,7 +47,7 @@ public static Builder builder(String... classNames) {
private ReflectiveClassBuildItem(boolean constructors, boolean queryConstructors, boolean methods, boolean queryMethods,
boolean fields, boolean getClasses, boolean weak, boolean serialization, boolean unsafeAllocated, String reason,
Class<?>... classes) {
this(constructors, queryConstructors, methods, queryMethods, fields, getClasses, weak, serialization,
this(constructors, queryConstructors, methods, queryMethods, false, fields, getClasses, weak, serialization,
unsafeAllocated, reason, stream(classes).map(Class::getName).toArray(String[]::new));
}

Expand Down Expand Up @@ -118,12 +119,12 @@ public static ReflectiveClassBuildItem serializationClass(String... classNames)
ReflectiveClassBuildItem(boolean constructors, boolean queryConstructors, boolean methods, boolean queryMethods,
boolean fields, boolean weak, boolean serialization,
boolean unsafeAllocated, String... className) {
this(constructors, queryConstructors, methods, queryMethods, fields, false, weak, serialization, unsafeAllocated,
this(constructors, queryConstructors, methods, queryMethods, false, fields, false, weak, serialization, unsafeAllocated,
null, className);
}

ReflectiveClassBuildItem(boolean constructors, boolean queryConstructors, boolean methods, boolean queryMethods,
boolean fields, boolean classes, boolean weak, boolean serialization,
boolean queryPublicMethods, boolean fields, boolean classes, boolean weak, boolean serialization,
boolean unsafeAllocated, String reason, String... className) {
for (String i : className) {
if (i == null) {
Expand All @@ -140,6 +141,7 @@ public static ReflectiveClassBuildItem serializationClass(String... classNames)
} else {
this.queryMethods = queryMethods;
}
this.queryPublicMethods = queryPublicMethods;
this.fields = fields;
this.classes = classes;
this.constructors = constructors;
Expand Down Expand Up @@ -169,6 +171,10 @@ public boolean isQueryMethods() {
return queryMethods;
}

public boolean isQueryPublicMethods() {
return queryPublicMethods;
}

public boolean isFields() {
return fields;
}
Expand Down Expand Up @@ -216,6 +222,7 @@ public static class Builder {
private boolean queryConstructors;
private boolean methods;
private boolean queryMethods;
private boolean queryPublicMethods;
private boolean fields;
private boolean classes;
private boolean weak;
Expand Down Expand Up @@ -284,6 +291,20 @@ public Builder queryMethods() {
return queryMethods(true);
}

/**
* Configures whether declared methods should be registered for reflection, for query purposes only,
* i.e. {@link Class#getMethods()}. Setting this enables getting all declared methods for the class but
* does not allow invoking them reflectively.
*/
public Builder queryPublicMethods(boolean queryPublicMethods) {
this.queryPublicMethods = queryPublicMethods;
return this;
}

public Builder queryPublicMethods() {
return queryPublicMethods(true);
}

/**
* Configures whether fields should be registered for reflection.
* Setting this enables getting all declared fields for the class as well as accessing them reflectively.
Expand Down Expand Up @@ -358,8 +379,8 @@ public Builder unsafeAllocated() {
}

public ReflectiveClassBuildItem build() {
return new ReflectiveClassBuildItem(constructors, queryConstructors, methods, queryMethods, fields, classes, weak,
serialization, unsafeAllocated, reason, className);
return new ReflectiveClassBuildItem(constructors, queryConstructors, methods, queryMethods, queryPublicMethods,
fields, classes, weak, serialization, unsafeAllocated, reason, className);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ void generateReflectConfig(BuildProducer<GeneratedResourceBuildItem> reflectConf
extractToJsonArray(info.queriedMethodSet, queriedMethodsArray);
}
}
if (info.queryPublicMethods) {
json.put("queryAllPublicMethods", true);
}
if (!methodsArray.isEmpty()) {
json.put("methods", methodsArray);
}
Expand Down Expand Up @@ -206,6 +209,9 @@ public void addReflectiveClass(Map<String, ReflectionInfo> reflectiveClasses, Se
if (classBuildItem.isQueryMethods()) {
existing.queryMethods = true;
}
if (classBuildItem.isQueryPublicMethods()) {
existing.queryPublicMethods = true;
}
if (classBuildItem.isFields()) {
existing.fields = true;
}
Expand Down Expand Up @@ -249,6 +255,7 @@ static final class ReflectionInfo {
boolean queryConstructors;
boolean methods;
boolean queryMethods;
boolean queryPublicMethods;
boolean fields;
boolean classes;
boolean serialization;
Expand All @@ -266,6 +273,7 @@ private ReflectionInfo() {
private ReflectionInfo(ReflectiveClassBuildItem classBuildItem, String typeReachable) {
this.methods = classBuildItem.isMethods();
this.queryMethods = classBuildItem.isQueryMethods();
this.queryPublicMethods = classBuildItem.isQueryPublicMethods();
this.fields = classBuildItem.isFields();
this.classes = classBuildItem.isClasses();
this.typeReachable = typeReachable;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package io.quarkus.smallrye.faulttolerance.deployment;

import java.time.temporal.ChronoUnit;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Queue;
import java.util.Set;

import jakarta.annotation.Priority;
Expand Down Expand Up @@ -88,7 +86,6 @@ public void build(BuildProducer<AnnotationsTransformerBuildItem> annotationsTran
BuildProducer<SystemPropertyBuildItem> systemProperty,
CombinedIndexBuildItem combinedIndexBuildItem,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<ReflectiveMethodBuildItem> reflectiveMethod,
BuildProducer<RunTimeConfigurationDefaultBuildItem> config,
BuildProducer<RuntimeInitializedClassBuildItem> runtimeInitializedClassBuildItems) {

Expand Down Expand Up @@ -120,43 +117,6 @@ public void build(BuildProducer<AnnotationsTransformerBuildItem> annotationsTran
}
beans.produce(handlerBeans.build());
}
// Add reflective access to fallback methods
for (AnnotationInstance annotation : index.getAnnotations(DotNames.FALLBACK)) {
AnnotationValue fallbackMethodValue = annotation.value("fallbackMethod");
if (fallbackMethodValue == null) {
continue;
}
String fallbackMethod = fallbackMethodValue.asString();

Queue<DotName> classesToScan = new ArrayDeque<>(); // work queue

// @Fallback can only be present on methods, so this is just future-proofing
AnnotationTarget target = annotation.target();
if (target.kind() == Kind.METHOD) {
classesToScan.add(target.asMethod().declaringClass().name());
}

while (!classesToScan.isEmpty()) {
DotName name = classesToScan.poll();
ClassInfo clazz = index.getClassByName(name);
if (clazz == null) {
continue;
}

// we could further restrict the set of registered methods based on matching parameter types,
// but that's relatively complex and SmallRye Fault Tolerance has to do it anyway
clazz.methods()
.stream()
.filter(it -> fallbackMethod.equals(it.name()))
.forEach(it -> reflectiveMethod.produce(new ReflectiveMethodBuildItem(getClass().getName(), it)));

DotName superClass = clazz.superName();
if (superClass != null && !DotNames.OBJECT.equals(superClass)) {
classesToScan.add(superClass);
}
classesToScan.addAll(clazz.interfaceNames());
}
}
// Add reflective access to custom backoff strategies
for (ClassInfo strategy : index.getAllKnownImplementors(DotNames.CUSTOM_BACKOFF_STRATEGY)) {
reflectiveClass.produce(ReflectiveClassBuildItem.builder(strategy.name().toString()).methods().build());
Expand Down Expand Up @@ -260,6 +220,67 @@ public void transform(TransformationContext ctx) {
});
}

@BuildStep
// Add reflective access to fallback methods, use bean index since fallback methods can be in bean classes
void processFallbackMethodsAndClases(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<ReflectiveMethodBuildItem> reflectiveMethod) {
IndexView index = beanArchiveIndexBuildItem.getIndex();
Map<DotName, Set<String>> classesToScan = new HashMap<>();

for (AnnotationInstance annotation : index.getAnnotations(DotNames.FALLBACK)) {
AnnotationValue fallbackMethodValue = annotation.value("fallbackMethod");
if (fallbackMethodValue == null) {
continue;
}
String fallbackMethod = fallbackMethodValue.asString();

// @Fallback can only be present on methods, so this is just future-proofing
AnnotationTarget target = annotation.target();
final ClassInfo clazz = target.asMethod().declaringClass();
if (target.kind() != Kind.METHOD) {
continue;
}

// Scan both the hierarchy of the declaring class and its (super)interfaces like in
// io.smallrye.faulttolerance.internal.SecurityActions.findDeclaredMethodNames
DotName name = clazz.name();
while (name != null && !DotNames.OBJECT.equals(name)) {
classesToScan.computeIfAbsent(name, k1 -> new HashSet<>()).add(fallbackMethod);
clazz.interfaceNames()
.forEach(it -> classesToScan.computeIfAbsent(it, k -> new HashSet<>()).add(fallbackMethod));
ClassInfo classInfo = index.getClassByName(name);
if (classInfo == null) {
break;
}
name = classInfo.superName();
}
}

for (Map.Entry<DotName, Set<String>> entry : classesToScan.entrySet()) {
DotName name = entry.getKey();
Set<String> methods = entry.getValue();
ClassInfo classInfo = index.getClassByName(name);

// io.smallrye.faulttolerance.config.FaultToleranceOperation.validate reflectively accesses methods and
// interfaces of the bean class and its superclasses through
// io.smallrye.faulttolerance.internal.SecurityActions.findDeclaredMethodNames
if (classInfo.isInterface()) {
// for interfaces getMethods() is invoked in addition to getDeclaredMethods()
reflectiveClass.produce(ReflectiveClassBuildItem.builder(name.toString()).queryPublicMethods().build());
}
reflectiveClass.produce(ReflectiveClassBuildItem.builder(name.toString()).queryMethods().build());

// we could further restrict the set of registered methods based on matching parameter types,
// but that's relatively complex and SmallRye Fault Tolerance has to do it anyway
classInfo.methods()
.stream()
.filter(it -> methods.contains(it.name()))
.forEach(it -> reflectiveMethod.produce(new ReflectiveMethodBuildItem(it)));
}

}

@BuildStep
// needs to be RUNTIME_INIT because we need to read MP Config
@Record(ExecutionTime.RUNTIME_INIT)
Expand Down Expand Up @@ -288,6 +309,7 @@ void processFaultToleranceAnnotations(SmallRyeFaultToleranceRecorder recorder,

AnnotationStore annotationStore = validationPhase.getContext().get(BuildExtension.Key.ANNOTATION_STORE);
IndexView index = beanArchiveIndexBuildItem.getIndex();

// only generating annotation literal classes for MicroProfile/SmallRye Fault Tolerance annotations,
// none of them are application classes
ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.gizmo.ClassOutput;
Expand Down Expand Up @@ -274,4 +276,20 @@ private Class<?> tryLoad(String name, ClassLoader tccl) {
throw new IllegalStateException("Unable to load type: " + name, e);
}
}

@BuildStep
void registerNativeImageResources(BuildProducer<NativeImageResourceBuildItem> resources) {
// Accessed by io.vertx.core.impl.VertxBuilder.<init>
resources.produce(new NativeImageResourceBuildItem("META-INF/services/io.vertx.core.spi.VertxServiceProvider"));
// Accessed by io.vertx.core.impl.VertxImpl.<init>
resources.produce(new NativeImageResourceBuildItem("META-INF/services/io.vertx.core.spi.VerticleFactory"));
}

@BuildStep
void registerReflectivelyAccessedMethods(BuildProducer<ReflectiveMethodBuildItem> reflectiveMethods) {
// Accessed by io.vertx.core.impl.VertxImpl.<init>
reflectiveMethods.produce(new ReflectiveMethodBuildItem("java.lang.Thread$Builder$OfVirtual", "name",
String.class, long.class));
reflectiveMethods.produce(new ReflectiveMethodBuildItem("java.lang.Thread$Builder", "factory", new Class[0]));
}
}