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

io.micronaut.openapi.visitor.OpenApiExtraSchemaVisitor Maven / Gradle / Ivy

/*
 * Copyright 2017-2023 original authors
 *
 * 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
 *
 * https://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.micronaut.openapi.visitor;

import io.micronaut.core.annotation.Internal;
import io.micronaut.core.order.Ordered;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.visitor.TypeElementVisitor;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.openapi.annotation.OpenAPIExtraSchema;
import io.micronaut.openapi.annotation.OpenAPIExtraSchemas;
import io.swagger.v3.oas.models.media.Schema;

import javax.annotation.processing.SupportedOptions;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import static io.micronaut.core.util.ArrayUtils.isEmpty;
import static io.micronaut.core.util.ArrayUtils.isNotEmpty;
import static io.micronaut.openapi.visitor.ConfigUtils.isExtraSchemasEnabled;
import static io.micronaut.openapi.visitor.ConfigUtils.isOpenApiEnabled;
import static io.micronaut.openapi.visitor.ConfigUtils.isSpecGenerationEnabled;
import static io.micronaut.openapi.visitor.ContextUtils.getClassElements;
import static io.micronaut.openapi.visitor.ElementUtils.stringValue;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_ENABLED;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_NAME;
import static io.micronaut.openapi.visitor.SchemaDefinitionUtils.computeDefaultSchemaName;
import static io.micronaut.openapi.visitor.SchemaDefinitionUtils.getSchemaDefinition;
import static io.micronaut.openapi.visitor.SchemaUtils.COMPONENTS_SCHEMAS_PREFIX;
import static io.micronaut.openapi.visitor.SchemaUtils.resolveSchemas;
import static io.micronaut.openapi.visitor.Utils.resolveOpenApi;

/**
 * A {@link TypeElementVisitor} that builds the extra Open API schema definitions included by @{@link OpenAPIExtraSchema}
 * at the compile time.
 *
 * @since 6.12.0
 */
@Internal
@SupportedOptions(MICRONAUT_OPENAPI_ENABLED)
public class OpenApiExtraSchemaVisitor implements TypeElementVisitor {

    private static Map extraSchemas = new LinkedHashMap<>();
    private static Map extraSchemaClassnamesToNames = new LinkedHashMap<>();
    private static Set excludedExtraSchemaClassNames = new LinkedHashSet<>();
    private static Set excludedExtraSchemas = new LinkedHashSet<>();
    private static Set excludedExtraSchemaPackages = new LinkedHashSet<>();

    @Override
    public Set getSupportedAnnotationNames() {
        return CollectionUtils.setOf(
            OpenAPIExtraSchema.class.getName(),
            OpenAPIExtraSchemas.class.getName()
        );
    }

    @Override
    public void start(VisitorContext context) {
        Utils.init(context);
    }

    @Override
    public void finish(VisitorContext context) {

        if (!isOpenApiEnabled(context) || !isSpecGenerationEnabled(context) || !isExtraSchemasEnabled(context)) {
            return;
        }

        for (var excludedExtraSchemaPackage : excludedExtraSchemaPackages) {
            for (var entry : extraSchemaClassnamesToNames.entrySet()) {
                addToExcludeExtraSchema(entry.getKey(), excludedExtraSchemaPackage, context);
            }
            for (var entry : SchemaDefinitionUtils.getSchemaNameToClassNameMap().entrySet()) {
                addToExcludeExtraSchema(entry.getValue(), excludedExtraSchemaPackage, context);
            }
        }

        // remove excluded extra schemas
        for (var excludedExtraSchemaClassName : excludedExtraSchemaClassNames) {
            var schemaName = extraSchemaClassnamesToNames.get(excludedExtraSchemaClassName);
            if (schemaName == null) {
                for (var entry : SchemaDefinitionUtils.getSchemaNameToClassNameMap().entrySet()) {
                    if (entry.getValue().equals(excludedExtraSchemaClassName)) {
                        schemaName = entry.getKey();
                        break;
                    }
                }
            }
            if (schemaName != null) {
                extraSchemas.remove(schemaName);
                excludedExtraSchemas.add(schemaName);
            }
        }
    }

    private void addToExcludeExtraSchema(String className, String excludedExtraSchemaPackage, VisitorContext context) {
        var classEl = ContextUtils.getClassElement(className, context);
        if (classEl != null && classEl.getPackageName().equals(excludedExtraSchemaPackage)) {
            excludedExtraSchemaClassNames.add(className);
        }
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

    @Override
    public void visitClass(ClassElement element, VisitorContext context) {
        if (!isOpenApiEnabled(context) || !isSpecGenerationEnabled(context) || !isExtraSchemasEnabled(context)) {
            return;
        }

        for (var extraSchemaAnn : element.getAnnotationValuesByType(OpenAPIExtraSchema.class)) {
            String[] classes = extraSchemaAnn.stringValues();
            String[] excludeClasses = extraSchemaAnn.stringValues("excludeClasses");
            String[] excludeClassNames = extraSchemaAnn.stringValues("excludeClassNames");
            String[] packages = extraSchemaAnn.stringValues("packages");
            String[] excludePackages = extraSchemaAnn.stringValues("excludePackages");

            // Classes with annotation without these members we process as extra schema
            if (isEmpty(classes)
                && isEmpty(excludeClasses)
                && isEmpty(excludeClassNames)
                && isEmpty(packages)
                && isEmpty(excludePackages)) {
                processExtraSchemaClass(element, context);
                continue;
            }
            if (isNotEmpty(excludeClasses)) {
                excludedExtraSchemaClassNames.addAll(Arrays.asList(excludeClasses));
            }
            if (isNotEmpty(excludeClassNames)) {
                excludedExtraSchemaClassNames.addAll(Arrays.asList(excludeClassNames));
            }
            if (isNotEmpty(excludePackages)) {
                excludedExtraSchemaPackages.addAll(Arrays.asList(excludePackages));
            }
            if (isNotEmpty(packages)) {
                for (var packageName : packages) {
                    if (packageName.endsWith(".*")) {
                        packageName = packageName.substring(0, packageName.length() - 2);
                    }
                    var classEls = getClassElements(packageName, context);
                    for (var classEl : classEls) {
                        processExtraSchemaClass(classEl, context);
                    }
                }
            }
            if (isNotEmpty(classes)) {
                for (var className : classes) {
                    var classEl = ContextUtils.getClassElement(className, context);
                    processExtraSchemaClass(classEl, context);
                }
            }
        }
    }

    private void processExtraSchemaClass(ClassElement classEl, VisitorContext context) {
        if (classEl == null) {
            return;
        }
        String schemaName = computeDefaultSchemaName(stringValue(classEl, io.swagger.v3.oas.annotations.media.Schema.class, PROP_NAME).orElse(null),
            null, classEl, classEl.getTypeArguments(), context, null);
        var schema = getSchemaDefinition(resolveOpenApi(context), context, classEl, classEl.getTypeArguments(), null, Collections.emptyList(), null);
        if (schema == null) {
            return;
        }
        if (schema.get$ref().equals(COMPONENTS_SCHEMAS_PREFIX + schemaName)) {
            var schemas = resolveSchemas(Utils.resolveOpenApi(context));
            schema = schemas.get(schemaName);
        }
        extraSchemas.put(schemaName, schema);
        extraSchemaClassnamesToNames.put(classEl.getName(), schemaName);
    }

    public static Map getExtraSchemas() {
        return extraSchemas;
    }

    public static Collection getExcludedExtraSchemas() {
        return excludedExtraSchemas;
    }

    public static void clean() {
        extraSchemas = new LinkedHashMap<>();
        extraSchemaClassnamesToNames = new LinkedHashMap<>();
        excludedExtraSchemaClassNames = new LinkedHashSet<>();
        excludedExtraSchemas = new LinkedHashSet<>();
        excludedExtraSchemaPackages = new LinkedHashSet<>();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy