All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fnproject.fn.nativeimagesupport.JacksonFeature Maven / Gradle / Ivy

/*
 * Copyright (c) 2019, 2020, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * 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 com.fnproject.fn.nativeimagesupport;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.fasterxml.jackson.annotation.JacksonAnnotation;
import com.oracle.svm.reflect.hosted.ReflectionFeature;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;

/**
 * This is a graal native-image feature that automatically includes any classes referenced as literals in Jackson Annotations
 * from included classes.
 * 

* The process assumes that the following classes require full reflective acceess (all fields/methods/constructors) : *

    *
  • Classes with Jackson annotations (any jackson annotation on the class or in a member)
  • *
  • Any Class literals referenced from jackson annotations or their descendents (e.g. Serializers, dispatch types)
  • *
*

* This is not likely to be complete and may skip annotations in some cases, notably there are cases where super classes may not be correctly accounted for. */ public class JacksonFeature implements Feature { public JacksonFeature() { System.out.println("JacksonFeature: FnProject experimental Jackson feature loaded"); System.out.println("JacksonFeature: Graal native image support is *experimental* it may not be stable and there may be cases where it does not work as expected"); } private static final String JACKSON_PACKAGE_PREFIX = "com.fasterxml.jackson"; private static boolean shouldIncludeClass(Class clz) { return !clz.isInterface() && clz != Void.class && !clz.getPackage().getName().startsWith(JACKSON_PACKAGE_PREFIX); } private static boolean isJacksonAnnotation(Annotation a) { return a.annotationType().getAnnotation(JacksonAnnotation.class) != null; } protected static Stream> extractLiteralAnnotationRefs(Annotation a) { Class aClass = a.annotationType(); return Arrays.stream(aClass.getDeclaredMethods()) .flatMap(m -> { Object val; // get annotation attribute value try { val = m.invoke(a); } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException e) { throw new IllegalStateException("Failed to retrieve annotation value from annotation" + a + " method " + m, e); } // technically annotations can't be null but just in case if (val == null) { return Stream.empty(); } if (val.getClass().isAnnotation()) { // annotation param on an annotation - descend return extractLiteralAnnotationRefs((Annotation) val); } else if (val.getClass().isArray()) { Class innerType = val.getClass().getComponentType(); if (innerType.isAnnotation()) { // list of annotations - descend return Arrays.stream((Annotation[]) val) .flatMap(JacksonFeature::extractLiteralAnnotationRefs); } return Arrays.stream((Object[]) val) // add class literals in array ref .filter(arrayVal -> arrayVal instanceof Class).map(arrayVal -> (Class) arrayVal); } else if (val instanceof Class) { return Stream.of((Class) val); } return Stream.empty(); }); } // VisibleForTesting protected static Stream> expandClassesToMarkForReflection(Class clazz) { List jacksonAnnotations; try { jacksonAnnotations = Stream.concat(Stream.concat( Arrays.stream(clazz.getAnnotations()), Arrays.stream(clazz.getDeclaredFields()).flatMap(f -> Arrays.stream(f.getAnnotations()))), Arrays.stream(clazz.getDeclaredMethods()).flatMap(m -> Arrays.stream(m.getAnnotations())) ).filter(JacksonFeature::isJacksonAnnotation).collect(Collectors.toList()); } catch (NoClassDefFoundError ignored) { // we skip the whole class if any of its members are unresolvable - this is assumed safe as jackson won't be able to load the class here anyway return Stream.empty(); } // if no jackson classes present, skip the whole class if (jacksonAnnotations.isEmpty()) { return Stream.empty(); } // otherwise include the class and any descendent classes referenced within those annotations return Stream.concat(Stream.of(clazz), jacksonAnnotations.stream() .flatMap(JacksonFeature::extractLiteralAnnotationRefs)) .filter(JacksonFeature::shouldIncludeClass); } @Override public List> getRequiredFeatures() { List> fs = new ArrayList<>(); fs.add(ReflectionFeature.class); return fs; } @Override public void beforeAnalysis(BeforeAnalysisAccess access) { ClassLoader cl = access.getApplicationClassLoader(); RuntimeReflectionSupport rrs = ImageSingletons.lookup(RuntimeReflectionSupport.class); access.registerSubtypeReachabilityHandler((acc, sourceClazz) -> { expandClassesToMarkForReflection(sourceClazz) .forEach((referencedClazz) -> { System.out.println("JacksonFeature: adding extra Jackson annotated " + referencedClazz); acc.registerAsUsed(referencedClazz); acc.registerAsInHeap(referencedClazz); rrs.register(referencedClazz); rrs.register(referencedClazz.getDeclaredConstructors()); rrs.register(referencedClazz.getDeclaredMethods()); Arrays.stream(referencedClazz.getDeclaredFields()).forEach(f -> rrs.register(false, f)); }); }, Object.class); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy