diff --git a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java index 164c0b5fa67..bef592152d0 100644 --- a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java +++ b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java @@ -116,6 +116,9 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { // all classes, fields and methods annotated with @Reflected addAnnotatedWithReflected(context); + // all classes used as return types and parameters in JAX-RS resources + processJaxRsTypes(context); + // JAX-RS types required for headers, query params etc. addJaxRsConversions(context); @@ -222,6 +225,17 @@ private void addJaxRsConversions(BeforeAnalysisContext context, String annotatio } } + private void processJaxRsTypes(BeforeAnalysisContext context) { + tracer.parsing(() -> "Looking up JAX-RS resource methods."); + + new JaxRsMethodAnalyzer(context, util) + .find() + .forEach(it -> { + tracer.parsing(() -> " class " + it); + context.register(it).addAll(); + }); + } + private void addAnnotatedWithReflected(BeforeAnalysisContext context) { // want to make sure we use the correct classloader String annotation = Reflected.class.getName(); @@ -461,7 +475,7 @@ private void superclasses(BeforeAnalysisContext context, Class aClass) { } } - private final class BeforeAnalysisContext { + final class BeforeAnalysisContext { private final BeforeAnalysisAccess access; private final Set> processed = new HashSet<>(); private final Set> excluded = new HashSet<>(); diff --git a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/JaxRsMethodAnalyzer.java b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/JaxRsMethodAnalyzer.java new file mode 100644 index 00000000000..2b833206bbe --- /dev/null +++ b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/JaxRsMethodAnalyzer.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.integrations.graal.nativeimage.extension; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ClassInfoList; +import io.github.classgraph.ClassRefTypeSignature; +import io.github.classgraph.MethodInfo; +import io.github.classgraph.MethodInfoList; +import io.github.classgraph.MethodParameterInfo; +import io.github.classgraph.TypeArgument; +import io.github.classgraph.TypeSignature; + +/* + * Analyzes JAX-RS resource method signatures and returns all types to be added for reflection. + */ +class JaxRsMethodAnalyzer { + /* + what we ignore: + - JsonObject and JsonArray - part of JSON-P handling, which works without reflection + - Response - part of JAX-RS and works already + - String - no need to add it + - Map, List, Set - basic collection classes - we only care about the type parameter, not the collection itself + */ + private static final Set IGNORED_TYPES = Set.of("javax.json.JsonObject", + "javax.json.JsonArray", + "javax.ws.rs.core.Response", + "java.lang.String", + Map.class.getName(), + List.class.getName(), + Set.class.getName()); + + private static final String HTTP_METHOD_ANNOTATION = "javax.ws.rs.HttpMethod"; + + private final Set classesToAdd = new HashSet<>(); + private final HelidonReflectionFeature.BeforeAnalysisContext context; + private final NativeUtil nativeUtil; + + JaxRsMethodAnalyzer(HelidonReflectionFeature.BeforeAnalysisContext context, + NativeUtil nativeUtil) { + this.context = context; + this.nativeUtil = nativeUtil; + } + + Set> find() { + ClassInfoList classes = context.scan() + .getClassesWithMethodAnnotation(HTTP_METHOD_ANNOTATION); + + for (ClassInfo aClass : classes) { + MethodInfoList methods = aClass.getMethodInfo(); + for (MethodInfo method : methods) { + if (method.hasAnnotation(HTTP_METHOD_ANNOTATION)) { + add(method.getTypeSignatureOrTypeDescriptor() + .getResultType()); + + MethodParameterInfo[] parameterInfo = method.getParameterInfo(); + for (MethodParameterInfo param : parameterInfo) { + if (param.getAnnotationInfo().isEmpty()) { + add(param.getTypeSignatureOrTypeDescriptor()); + } + } + } + } + } + Set result = Set.copyOf(classesToAdd); + classesToAdd.clear(); + + return result.stream() + .map(nativeUtil.classMapper("jaxrs-result-or-param")) + .filter(Objects::nonNull) + .filter(nativeUtil.inclusionFilter("jaxrs-result-or-param")) + .collect(Collectors.toSet()); + } + + void add(TypeSignature type) { + if (type instanceof ClassRefTypeSignature) { + ClassRefTypeSignature crts = (ClassRefTypeSignature) type; + if (add(crts.getFullyQualifiedClassName())) { + List typeArgs = crts.getTypeArguments(); + if (typeArgs != null && !typeArgs.isEmpty()) { + typeArgs.forEach(it -> add(it.getTypeSignature())); + } + } + } else { + add(type.toString()); + } + } + + boolean add(String type) { + if (!IGNORED_TYPES.contains(type)) { + return classesToAdd.add(type); + } + return true; + } +} diff --git a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/NativeUtil.java b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/NativeUtil.java index 85e166ca059..a9d254ab336 100644 --- a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/NativeUtil.java +++ b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/NativeUtil.java @@ -277,6 +277,14 @@ Set> findSubclasses(String superclassName) { } } + Predicate> inclusionFilter(String description) { + return new InclusionFilter(tracer, exclusion, description); + } + + Function> classMapper(String description) { + return new StringClassResolverMapper(tracer, classResolver, description); + } + private static class InclusionFilter implements Predicate> { private final NativeTrace tracer; private final Function, Boolean> exclusion; @@ -301,6 +309,27 @@ public boolean test(Class aClass) { } } + private static class StringClassResolverMapper implements Function> { + private final NativeTrace tracer; + private final Function> classResolver; + private final String message; + + StringClassResolverMapper(NativeTrace tracer, Function> classResolver, String message) { + this.tracer = tracer; + this.classResolver = classResolver; + this.message = message; + } + + @Override + public Class apply(String className) { + Class clazz = classResolver.apply(className); + if (clazz == null) { + tracer.parsing(() -> " class " + className + " " + message + " is not on classpath."); + } + return clazz; + } + } + private static class ClassResolverMapper implements Function> { private final NativeTrace tracer; private final Function> classResolver;