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

io.smallrye.openapi.runtime.scanner.dataobject.IgnoreResolver Maven / Gradle / Ivy

/*
 * Copyright 2018 Red Hat, Inc, and individual contributors.
 *
 * 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.smallrye.openapi.runtime.scanner.dataobject;

import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;

import io.smallrye.openapi.api.OpenApiConstants;
import io.smallrye.openapi.runtime.scanner.dataobject.DataObjectDeque.PathEntry;
import io.smallrye.openapi.runtime.util.JandexUtil;
import io.smallrye.openapi.runtime.util.TypeUtil;

/**
 * @author Marc Savy {@literal }
 */
public class IgnoreResolver {

    private static final Logger LOG = Logger.getLogger(IgnoreResolver.class);
    private final Map IGNORE_ANNOTATION_MAP = new LinkedHashMap<>();
    private final AugmentedIndexView index;

    {
        IgnoreAnnotationHandler[] ignoreHandlers = {
                new SchemaHiddenHandler(),
                new JsonbTransientHandler(),
                new JsonIgnorePropertiesHandler(),
                new JsonIgnoreHandler(),
                new JsonIgnoreTypeHandler(),
                new TransientIgnoreHandler()
        };

        for (IgnoreAnnotationHandler handler : ignoreHandlers) {
            IGNORE_ANNOTATION_MAP.put(handler.getName(), handler);
        }
    }

    public IgnoreResolver(AugmentedIndexView index) {
        this.index = index;
    }

    public boolean isIgnore(AnnotationTarget annotationTarget, DataObjectDeque.PathEntry pathEntry) {
        for (IgnoreAnnotationHandler handler : IGNORE_ANNOTATION_MAP.values()) {
            boolean result = handler.shouldIgnore(annotationTarget, pathEntry);
            if (result) {
                return true;
            }
        }
        return false;
    }

    /**
     * Handler for OAS hidden @{@link Schema}
     */
    private final class SchemaHiddenHandler implements IgnoreAnnotationHandler {
        @Override
        public boolean shouldIgnore(AnnotationTarget target, PathEntry parentPathEntry) {
            AnnotationInstance annotationInstance = TypeUtil.getAnnotation(target, getName());

            if (annotationInstance != null) {
                Boolean isHidden = JandexUtil.booleanValue(annotationInstance,
                        OpenApiConstants.PROP_HIDDEN);

                if (isHidden != null) {
                    return isHidden;
                }
            }

            return false;
        }

        @Override
        public DotName getName() {
            return OpenApiConstants.DOTNAME_SCHEMA;
        }
    }

    /**
     * Handler for JSON-B's @{@link javax.json.bind.annotation.JsonbTransient}
     */
    private final class JsonbTransientHandler implements IgnoreAnnotationHandler {
        @Override
        public boolean shouldIgnore(AnnotationTarget target, DataObjectDeque.PathEntry parentPathEntry) {
            return TypeUtil.hasAnnotation(target, getName());
        }

        @Override
        public DotName getName() {
            return OpenApiConstants.DOTNAME_JSONB_TRANSIENT;
        }
    }

    /**
     * Handler for Jackson's {@link com.fasterxml.jackson.annotation.JsonIgnoreProperties JsonIgnoreProperties}
     */
    private final class JsonIgnorePropertiesHandler implements IgnoreAnnotationHandler {

        @Override
        public boolean shouldIgnore(AnnotationTarget target, DataObjectDeque.PathEntry parentPathEntry) {
            if (declaringClassIgnore(target)) {
                return true;
            }

            return nestingPropertyIgnore(parentPathEntry.getAnnotationTarget(), propertyName(target));
        }

        /**
         * Declaring class ignore
         * 
         * 
         * 
         *  @JsonIgnoreProperties("ignoreMe")
         *  class A {
         *    String ignoreMe;
         *  }
         * 
         * 
* * @param target * @return */ private boolean declaringClassIgnore(AnnotationTarget target) { AnnotationInstance declaringClassJIP = TypeUtil.getAnnotation(TypeUtil.getDeclaringClass(target), getName()); return shouldIgnoreTarget(declaringClassJIP, propertyName(target)); } /** * Look for nested/enclosing type @com.fasterxml.jackson.annotation.JsonIgnoreProperties. * *
         * 
         * class A {
         *   @com.fasterxml.jackson.annotation.JsonIgnoreProperties("ignoreMe")
         *   B foo;
         * }
         *
         * class B {
         *   String ignoreMe; // Ignored during scan via A.
         *   String doNotIgnoreMe;
         * }
         * 
         * 
* * @param nesting * @param propertyName * @return */ private boolean nestingPropertyIgnore(AnnotationTarget nesting, String propertyName) { if (nesting == null) { return false; } AnnotationInstance nestedTypeJIP = TypeUtil.getAnnotation(nesting, getName()); return shouldIgnoreTarget(nestedTypeJIP, propertyName); } private String propertyName(AnnotationTarget target) { if (target.kind() == Kind.FIELD) { return target.asField().name(); } // Assuming this is a getter or setter String name = target.asMethod().name().substring(3); return Character.toLowerCase(name.charAt(0)) + name.substring(1); } private boolean shouldIgnoreTarget(AnnotationInstance jipAnnotation, String targetName) { if (jipAnnotation == null || jipAnnotation.value() == null) { return false; } String[] jipValues = jipAnnotation.value().asStringArray(); return Arrays.stream(jipValues).anyMatch(v -> v.equals(targetName)); } @Override public DotName getName() { return OpenApiConstants.DOTNAME_JACKSON_IGNORE_PROPERTIES; } } /** * Handler for Jackson's @{@link com.fasterxml.jackson.annotation.JsonIgnore JsonIgnore} */ private final class JsonIgnoreHandler implements IgnoreAnnotationHandler { @Override public boolean shouldIgnore(AnnotationTarget target, DataObjectDeque.PathEntry parentPathEntry) { AnnotationInstance annotationInstance = TypeUtil.getAnnotation(target, getName()); if (annotationInstance != null) { return valueAsBooleanOrTrue(annotationInstance); } return false; } @Override public DotName getName() { return OpenApiConstants.DOTNAME_JACKSON_IGNORE; } } /** * Handler for @{@link JsonIgnoreType} */ private final class JsonIgnoreTypeHandler implements IgnoreAnnotationHandler { private Set ignoredTypes = new LinkedHashSet<>(); @Override public boolean shouldIgnore(AnnotationTarget target, DataObjectDeque.PathEntry parentPathEntry) { Type classType; switch (target.kind()) { case FIELD: classType = target.asField().type(); break; case METHOD: MethodInfo method = target.asMethod(); if (method.returnType().kind().equals(Type.Kind.VOID)) { // Setter method classType = method.parameters().get(0); } else { // Getter method classType = method.returnType(); } break; default: return false; } // Primitive and non-indexed types will result in a null if (classType.kind() == Type.Kind.PRIMITIVE || classType.kind() == Type.Kind.VOID || (classType.kind() == Type.Kind.ARRAY && classType.asArrayType().component().kind() == Type.Kind.PRIMITIVE) || !index.containsClass(classType)) { return false; } // Find the real class implementation where the @JsonIgnoreType annotation may be. ClassInfo classInfo = index.getClass(classType); if (ignoredTypes.contains(classInfo.name())) { LOG.debugv("Ignoring type that is member of ignore set: {0}", classInfo.name()); return true; } AnnotationInstance annotationInstance = TypeUtil.getAnnotation(classInfo, getName()); if (annotationInstance != null && valueAsBooleanOrTrue(annotationInstance)) { // Add the ignored field or class name LOG.debugv("Ignoring type and adding to ignore set: {0}", classInfo.name()); ignoredTypes.add(classInfo.name()); return true; } return false; } @Override public DotName getName() { return OpenApiConstants.DOTNAME_JACKSON_IGNORE_TYPE; } } private final class TransientIgnoreHandler implements IgnoreAnnotationHandler { @Override public boolean shouldIgnore(AnnotationTarget target, PathEntry parentPathEntry) { if (target.kind() == AnnotationTarget.Kind.FIELD) { FieldInfo field = target.asField(); // If field has transient modifier, e.g. `transient String foo;`, then hide it. if (Modifier.isTransient(field.flags())) { // Unless field is annotated with @Schema to explicitly un-hide it. AnnotationInstance schemaAnnotation = TypeUtil.getSchemaAnnotation(target); if (schemaAnnotation != null) { Boolean boolVal = JandexUtil.booleanValue(schemaAnnotation, OpenApiConstants.PROP_HIDDEN); if (boolVal == null) { return true; } else { return boolVal; } } return true; } } return false; } @Override public DotName getName() { return DotName.createSimple(TransientIgnoreHandler.class.getName()); } } private boolean valueAsBooleanOrTrue(AnnotationInstance annotation) { return Optional.ofNullable(annotation.value()) .map(AnnotationValue::asBoolean) .orElse(true); } private interface IgnoreAnnotationHandler { boolean shouldIgnore(AnnotationTarget target, DataObjectDeque.PathEntry parentPathEntry); DotName getName(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy