diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java index f6c4778e6f508..d738e8f0f86c0 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/SmallRyeFaultToleranceProcessor.java @@ -1,7 +1,6 @@ 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; @@ -9,7 +8,6 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Queue; import java.util.Set; import jakarta.annotation.Priority; @@ -88,7 +86,6 @@ public void build(BuildProducer annotationsTran BuildProducer systemProperty, CombinedIndexBuildItem combinedIndexBuildItem, BuildProducer reflectiveClass, - BuildProducer reflectiveMethod, BuildProducer config, BuildProducer runtimeInitializedClassBuildItems) { @@ -120,43 +117,6 @@ public void build(BuildProducer 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 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(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()); @@ -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 reflectiveClass, + BuildProducer reflectiveMethod) { + IndexView index = beanArchiveIndexBuildItem.getIndex(); + Map> 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 interfaces like in + // io.smallrye.faulttolerance.internal.SecurityActions.findDeclaredMethodNames + DotName name = clazz.name(); + while (name != null && !DotNames.OBJECT.equals(name)) { + Set methods = classesToScan.computeIfAbsent(name, k -> new HashSet<>()); + methods.add(fallbackMethod); + ClassInfo classInfo = index.getClassByName(name); + if (classInfo == null) { + break; + } + name = classInfo.superName(); + } + clazz.interfaceNames().forEach(it -> classesToScan.computeIfAbsent(it, k -> new HashSet<>()).add(fallbackMethod)); + } + + for (Map.Entry> entry : classesToScan.entrySet()) { + DotName name = entry.getKey(); + Set 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) @@ -287,6 +308,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); diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java index 1c3d7fb2e9915..438add51a2f58 100644 --- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java @@ -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; @@ -274,4 +276,20 @@ private Class tryLoad(String name, ClassLoader tccl) { throw new IllegalStateException("Unable to load type: " + name, e); } } + + @BuildStep + void registerNativeImageResources(BuildProducer resources) { + // Accessed by io.vertx.core.impl.VertxBuilder. + resources.produce(new NativeImageResourceBuildItem("META-INF/services/io.vertx.core.spi.VertxServiceProvider")); + // Accessed by io.vertx.core.impl.VertxImpl. + resources.produce(new NativeImageResourceBuildItem("META-INF/services/io.vertx.core.spi.VerticleFactory")); + } + + @BuildStep + void registerReflectivelyAccessedMethods(BuildProducer reflectiveMethods) { + // Accessed by io.vertx.core.impl.VertxImpl. + 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])); + } }