From 567d9cfcb550a48aeca274a618e6e94d847c2e09 Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Mon, 29 Jun 2020 16:40:10 +0200 Subject: [PATCH] [#10295] Allow using the informer interface, and track down all types This change also evaluates implementations of `ResourceEventHandler`, which is the corresponding interface for the informer. This allows to use watchers or informers the same way. Also it traverses through the type information, adding all required types to the list of types enabling reflection. If only the direct class is added, deserialization will fail, unless all the references classes are manually annotated with @RegisterForReflection. Signed-off-by: Jens Reimann --- .../deployment/KubernetesClientProcessor.java | 83 ++++++++++++++----- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java index 1b994efe4f58c..b004defffbf94 100644 --- a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java +++ b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java @@ -1,11 +1,14 @@ package io.quarkus.kubernetes.client.deployment; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import javax.inject.Inject; +import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.Type; import org.jboss.logging.Logger; @@ -19,6 +22,7 @@ import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; import io.quarkus.deployment.util.JandexUtil; import io.quarkus.jackson.deployment.IgnoreJsonDeserializeClassBuildItem; import io.quarkus.kubernetes.client.runtime.KubernetesClientProducer; @@ -27,8 +31,12 @@ public class KubernetesClientProcessor { private static final DotName WATCHER = DotName.createSimple("io.fabric8.kubernetes.client.Watcher"); + private static final DotName RESOURCE_EVENT_HANDLER = DotName + .createSimple("io.fabric8.kubernetes.client.informers.ResourceEventHandler"); private static final DotName KUBERNETES_RESOURCE = DotName .createSimple("io.fabric8.kubernetes.api.model.KubernetesResource"); + private static final DotName KUBERNETES_RESOURCE_LIST = DotName + .createSimple("io.fabric8.kubernetes.api.model.KubernetesResourceList"); private static final Logger log = Logger.getLogger(KubernetesClientProcessor.class.getName()); @@ -38,6 +46,9 @@ public class KubernetesClientProcessor { @Inject BuildProducer reflectiveClasses; + @Inject + BuildProducer reflectiveHierarchies; + @Inject BuildProducer ignoredJsonDeserializationClasses; @@ -52,29 +63,19 @@ public void process(ApplicationIndexBuildItem applicationIndex, CombinedIndexBui featureProducer.produce(new FeatureBuildItem(Feature.KUBERNETES_CLIENT)); roleProducer.produce(new KubernetesRoleBuildItem("view")); - Set watchedClasses = new HashSet<>(); // make sure the watchers fully (and not weakly) register Kubernetes classes for reflection - applicationIndex.getIndex().getAllKnownImplementors(WATCHER) - .forEach(c -> { - try { - final List watcherGenericTypes = JandexUtil.resolveTypeParameters(c.name(), - WATCHER, combinedIndexBuildItem.getIndex()); - if (watcherGenericTypes.size() == 1) { - watchedClasses.add(watcherGenericTypes.get(0).name().toString()); - } - } catch (IllegalStateException ignored) { - // when the class has no subclasses and we were not able to determine the generic types, it's likely that - // the watcher will fail due to not being able to deserialize the class - if (applicationIndex.getIndex().getAllKnownSubclasses(c.name()).isEmpty()) { - log.warn("Watcher '" + c.name() + "' will most likely not work correctly in native mode. " + - "Consider specifying the generic type of 'io.fabric8.kubernetes.client.Watcher' that this class handles. " - + - "See https://quarkus.io/guides/kubernetes-client#note-on-implementing-the-watcher-interface for more details"); - } - } - }); - if (!watchedClasses.isEmpty()) { - reflectiveClasses.produce(new ReflectiveClassBuildItem(true, true, watchedClasses.toArray(new String[0]))); + final Set watchedClasses = new HashSet<>(); + findWatchedClasses(WATCHER, applicationIndex, combinedIndexBuildItem, watchedClasses); + findWatchedClasses(RESOURCE_EVENT_HANDLER, applicationIndex, combinedIndexBuildItem, watchedClasses); + + for (DotName className : watchedClasses) { + final ClassInfo watchedClass = combinedIndexBuildItem.getIndex().getClassByName(className); + if (watchedClass == null) { + log.warnv("Unable to lookup class: {0}", className); + } else { + reflectiveHierarchies + .produce(new ReflectiveHierarchyBuildItem(Type.create(watchedClass.name(), Type.Kind.CLASS))); + } } final String[] modelClasses = combinedIndexBuildItem.getIndex() @@ -85,8 +86,9 @@ public void process(ApplicationIndexBuildItem applicationIndex, CombinedIndexBui // since we are going to register them weakly ignoredJsonDeserializationClasses.produce(new IgnoreJsonDeserializeClassBuildItem(c.name())); }) - .map(c -> c.name().toString()) + .map(ClassInfo::name) .filter(c -> !watchedClasses.contains(c)) + .map(Object::toString) .toArray(String[]::new); reflectiveClasses.produce(ReflectiveClassBuildItem .builder(modelClasses).weak(true).methods(true).fields(false).build()); @@ -127,10 +129,45 @@ public void process(ApplicationIndexBuildItem applicationIndex, CombinedIndexBui reflectiveClasses .produce(new ReflectiveClassBuildItem(true, false, "io.fabric8.kubernetes.internal.KubernetesDeserializer")); + if (log.isDebugEnabled()) { + final String watchedClassNames = watchedClasses + .stream().map(Object::toString) + .sorted() + .collect(Collectors.joining("\n")); + log.debugv("Watched Classes:\n{0}", watchedClassNames); + Arrays.sort(modelClasses); + log.debugv("Model Classes:\n{0}", String.join("\n", modelClasses)); + } + // Enable SSL support by default sslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(Feature.KUBERNETES_CLIENT)); // wire up the KubernetesClient bean support additionalBeanBuildItemBuildItem.produce(AdditionalBeanBuildItem.unremovableOf(KubernetesClientProducer.class)); } + + private void findWatchedClasses(final DotName implementor, final ApplicationIndexBuildItem applicationIndex, + final CombinedIndexBuildItem combinedIndexBuildItem, final Set watchedClasses) { + applicationIndex.getIndex().getAllKnownImplementors(implementor) + .forEach(c -> { + try { + final List watcherGenericTypes = JandexUtil.resolveTypeParameters(c.name(), + implementor, combinedIndexBuildItem.getIndex()); + if (watcherGenericTypes.size() == 1) { + watchedClasses.add(watcherGenericTypes.get(0).name()); + } + } catch (IllegalStateException ignored) { + // when the class has no subclasses and we were not able to determine the generic types, it's likely that + // the watcher will fail due to not being able to deserialize the class + if (applicationIndex.getIndex().getAllKnownSubclasses(c.name()).isEmpty()) { + log.warnv("{0} '{1}' will most likely not work correctly in native mode. " + + "Consider specifying the generic type of '{2}' that this class handles. " + + + "See https://quarkus.io/guides/kubernetes-client#note-on-implementing-the-watcher-interface for more details", + implementor.local(), c.name(), implementor); + } + } + }); + } + }