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

org.spincast.plugins.openapi.bottomup.SpincastOpenApiManagerDefault Maven / Gradle / Ivy

package org.spincast.plugins.openapi.bottomup;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.PATCH;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spincast.core.exchange.RequestContext;
import org.spincast.core.json.JsonManager;
import org.spincast.core.routing.DefaultRouteParamAliasesBinder;
import org.spincast.core.routing.HttpMethod;
import org.spincast.core.routing.Route;
import org.spincast.core.routing.Router;
import org.spincast.core.routing.RoutingType;
import org.spincast.core.utils.SpincastStatics;
import org.spincast.core.websocket.WebsocketContext;
import org.spincast.plugins.openapi.bottomup.config.SpincastOpenApiBottomUpPluginConfig;
import org.spincast.plugins.openapi.bottomup.utils.SwaggerAnnotationsCreator;
import org.spincast.shaded.org.apache.commons.codec.digest.DigestUtils;
import org.spincast.shaded.org.apache.commons.lang3.StringUtils;

import com.google.common.collect.Sets;
import com.google.inject.Inject;

import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.jaxrs2.integration.JaxrsOpenApiContext;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.integration.SwaggerConfiguration;
import io.swagger.v3.oas.integration.api.OpenAPIConfiguration;
import io.swagger.v3.oas.integration.api.OpenApiScanner;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.implementation.StubMethod;

public class SpincastOpenApiManagerDefault, W extends WebsocketContext>
                                          implements SpincastOpenApiManager {

    protected final static Logger logger = LoggerFactory.getLogger(SpincastOpenApiManagerDefault.class);

    private final Router router;
    private final JsonManager jsonManager;
    private final SpincastOpenApiBottomUpPluginConfig spincastOpenApiBottomUpPluginConfig;
    private final SwaggerAnnotationsCreator annotationsCreator;
    private final DefaultRouteParamAliasesBinder defaultRouteParamAliasesBinder;
    private OpenAPI baseOpenApiInfo;
    private OpenAPI openApi;
    private String openApiAsJson;
    private String openApiAsJsonPretty;
    private String openApiAsYaml;
    private String openApiAsYamlPretty;
    private Set routeIdsToHide;
    private Set routeHttpMethodAndPathToHide;

    @Inject
    public SpincastOpenApiManagerDefault(Router router,
                                         JsonManager jsonManager,
                                         SpincastOpenApiBottomUpPluginConfig spincastOpenApiBottomUpPluginConfig,
                                         SwaggerAnnotationsCreator annotationsCreator,
                                         DefaultRouteParamAliasesBinder defaultRouteParamAliasesBinder) {
        this.router = router;
        this.jsonManager = jsonManager;
        this.spincastOpenApiBottomUpPluginConfig = spincastOpenApiBottomUpPluginConfig;
        this.annotationsCreator = annotationsCreator;
        this.defaultRouteParamAliasesBinder = defaultRouteParamAliasesBinder;
    }

    protected Router getRouter() {
        return this.router;
    }

    protected JsonManager getJsonManager() {
        return this.jsonManager;
    }

    protected SpincastOpenApiBottomUpPluginConfig getSpincastOpenApiBottomUpPluginConfig() {
        return this.spincastOpenApiBottomUpPluginConfig;
    }

    protected SwaggerAnnotationsCreator getAnnotationsCreator() {
        return this.annotationsCreator;
    }

    protected DefaultRouteParamAliasesBinder getDefaultRouteParamAliasesBinder() {
        return this.defaultRouteParamAliasesBinder;
    }

    public Set getRouteIdsToHide() {
        if (this.routeIdsToHide == null) {
            this.routeIdsToHide = new HashSet();
        }
        return this.routeIdsToHide;
    }

    public Set getRouteHttpMethodAndPathToHide() {
        if (this.routeHttpMethodAndPathToHide == null) {
            this.routeHttpMethodAndPathToHide = new HashSet();
        }
        return this.routeHttpMethodAndPathToHide;
    }

    /**
     * Delete cache so the Open API object
     * is computed from scratch.
     */
    @Override
    public void clearCache() {
        this.openApi = null;
        this.openApiAsJson = null;
        this.openApiAsJsonPretty = null;
        this.openApiAsYaml = null;
        this.openApiAsYamlPretty = null;
    }

    @Override
    public void resetAll() {
        this.baseOpenApiInfo = new OpenAPI();
        clearCache();
        getRouteIdsToHide().clear();
        getRouteHttpMethodAndPathToHide().clear();
    }

    @Override
    public void setOpenApiBase(OpenAPI baseOpenApiInfo) {
        this.baseOpenApiInfo = baseOpenApiInfo;
    }

    public OpenAPI getBaseOpenApiInfo() {
        return this.baseOpenApiInfo;
    }

    /**
     * @param force If true, cache
     * will be bypassed on updated.
     */
    @Override
    @Parameters
    public OpenAPI getOpenApi() {

        if (this.openApi == null) {
            try {

                OpenAPI baseInfos = getBaseOpenApiInfo();
                if (baseInfos == null) {
                    baseInfos = new OpenAPI();
                }

                //==========================================
                // Create JAX-RS like classes with the proper annotations.
                //==========================================
                Set> classes = generateJaxRsLikeClasses();
                OpenApiScanner scanner = createScanner(classes);

                //==========================================
                // Add the Paths specified as YAML Strings
                // on the routes.
                //==========================================
                addYamlStringSpecifiedPaths(baseInfos);

                SwaggerConfiguration swaggerConfiguration = new SwaggerConfiguration().openAPI(baseInfos);

                JaxrsOpenApiContext> context = new JaxrsOpenApiContext>();
                context.setOpenApiConfiguration(swaggerConfiguration);
                context.setOpenApiScanner(scanner);
                context.init();

                this.openApi = context.read();

            } catch (Exception ex) {
                throw SpincastStatics.runtimize(ex);
            }
        }

        return this.openApi;
    }

    protected OpenApiScanner createScanner(Set> classes) {

        OpenApiScanner scanner = new OpenApiScanner() {

            @Override
            public void setConfiguration(OpenAPIConfiguration openApiConfiguration) {
                // nothing required
            }

            @Override
            public Map resources() {
                return new HashMap();
            }

            @Override
            public Set> classes() {
                return classes;
            }
        };
        return scanner;
    }

    /**
     * This method will dynamically create and annotate
     * classes to simulate a JAX-RS application. This way,
     * we can reuse the logic from swagger-jaxrs2 to generate
     * the {@link OpenAPI} object.
     */
    protected Set> generateJaxRsLikeClasses() {

        //==========================================
        // Is the automatic specs generation disabled?
        //==========================================
        if (getSpincastOpenApiBottomUpPluginConfig().isDisableAutoSpecs()) {
            logger.info("Auto specs generation disabled from the configs.");
            return new HashSet>();
        }

        try {

            List> mainRoutes = getRouter().getMainRoutes();
            if (mainRoutes == null || mainRoutes.size() == 0) {
                return Sets.newHashSet();
            }

            Builder classBuilder =
                    new ByteBuddy().subclass(Object.class)
                                   .annotateType(AnnotationDescription.Builder.ofType(Path.class)
                                                                              .define("value", "")
                                                                              .build());

            Set operationIdsUsed = new HashSet();

            //==========================================
            // For each main route...
            //==========================================
            for (Route route : mainRoutes) {

                if (route.getRoutingTypes() != null && !route.getRoutingTypes().contains(RoutingType.FOUND)) {
                    logger.info("Route without routing type RoutingType.FOUND. Ignoring: " + route);
                    continue;
                }

                //==========================================
                // Resources route
                //==========================================
                if (route.isStaticResourceRoute()) {
                    logger.info("Static resources route. Ignoring: " + route);
                    continue;
                }

                //==========================================
                // Websocket routes are not supported for now.
                // @see https://github.com/OAI/OpenAPI-Specification/issues/523
                //==========================================
                if (route.isWebsocketRoute()) {
                    logger.info("Websocket routes not supported. Ignoring: " + route);
                    continue;
                }

                //==========================================
                // Hidden route?
                // From inline annotation or from id.
                //==========================================
                if (route.isSpecsIgnore() || isToHideFromId(route)) {
                    logger.info("Route hidden from specs, won't be added:" + route);
                    continue;
                }

                //==========================================
                // For each HTTP method...
                //==========================================
                if (route.getHttpMethods() == null || route.getHttpMethods().size() == 0) {
                    throw new RuntimeException("Not supposed. At least one HTTP method required!");
                }

                for (HttpMethod httpMethod : route.getHttpMethods()) {

                    //==========================================
                    // Is supported HTTP method?
                    //==========================================
                    if (!isSupportedHttpMethod(httpMethod)) {
                        logger.warn("Unmanaged HTTP method \"" + httpMethod + "\" will be ignored for route : " + route);
                        continue;
                    }

                    //==========================================
                    // Hidden route from HTTP method and path.
                    //==========================================
                    if (isToHideFromHttpMethodAndPath(route, httpMethod)) {
                        logger.info("Route hidden from specs, won't be added: [" + httpMethod.name() + "] " +
                                    route);
                        continue;
                    }

                    //==========================================
                    // Duplicate route
                    //==========================================
                    String operationId = createOperationId(route, httpMethod);
                    if (operationIdsUsed.contains(operationId)) {
                        logger.warn("Duplicate route, ignoring: " + route);
                        continue;
                    }
                    operationIdsUsed.add(operationId);

                    Object specs = route.getSpecs();

                    //==========================================
                    // Specs specified as a YAML string. It will
                    // be processed elsewhere.
                    //==========================================
                    if (specs != null && specs instanceof String) {
                        continue;
                    }

                    List handlerMethodAnnotations = new ArrayList();
                    Specs specsAnnotation = getSpecsAnnotation(route.getSpecs());

                    if (specs != null && specsAnnotation == null) {
                        logger.warn("The " + SpincastOpenApiBottomUpPlugin.class.getSimpleName() + " plugin currently only " +
                                    "understands a YAML string or an anonymous class annotated with @Specs " +
                                    "as the first parameter to the '.specs(...)' method when building a route." +
                                    "Neither was found on this route so we ignore it: " + route);
                        continue;
                    }

                    //==========================================
                    // @GET, @POST,... annotation
                    //==========================================
                    addHttpMethodAnnotation(handlerMethodAnnotations, httpMethod);

                    //==========================================
                    // @Path annotation
                    //==========================================
                    addPathAnnotationToHandlerMethod(handlerMethodAnnotations, route.getPath());

                    //==========================================
                    // @Consumes annotation
                    //==========================================
                    addConsumesAnnotationToHandlerMethod(handlerMethodAnnotations, specsAnnotation, route);

                    //==========================================
                    // @Produces annotation
                    //==========================================
                    addProducesAnnotationToHandlerMethod(handlerMethodAnnotations, specsAnnotation);

                    //==========================================
                    // @Operation annotation
                    //==========================================
                    addOperationAnnotationToHandlerMethod(handlerMethodAnnotations, specsAnnotation);

                    //==========================================
                    // @Parameters
                    //==========================================
                    addParametersAnnotationToHandlerMethod(handlerMethodAnnotations,
                                                           route.getPath(),
                                                           specsAnnotation);

                    classBuilder = classBuilder.defineMethod(operationId, void.class, Visibility.PUBLIC)
                                               .intercept(StubMethod.INSTANCE)
                                               .annotateMethod(handlerMethodAnnotations);
                }
            }

            Class clazz = classBuilder.make()
                                                        .load(getClass().getClassLoader())
                                                        .getLoaded();

            return Sets.newHashSet(clazz);

        } catch (Exception ex) {
            throw SpincastStatics.runtimize(ex);
        }
    }

    protected boolean isSupportedHttpMethod(HttpMethod httpMethod) {
        return httpMethod == HttpMethod.GET ||
               httpMethod == HttpMethod.POST ||
               httpMethod == HttpMethod.PUT ||
               httpMethod == HttpMethod.PATCH ||
               httpMethod == HttpMethod.DELETE ||
               httpMethod == HttpMethod.HEAD ||
               httpMethod == HttpMethod.OPTIONS;
    }

    protected Paths getPathsFromYamlString(String routePath, String specs) {

        String specsStr = getOpenApiPrefix() + "\n" +
                          "paths:\n" +
                          "  " + routePath + ":\n";

        String[] lines = specs.split("\\r?\\n");
        for (int i = 0; i < lines.length; i++) {
            specsStr += "    " + lines[i] + "\n";
        }

        SwaggerParseResult readContents = new OpenAPIV3Parser().readContents(specsStr);
        OpenAPI openAPI = readContents.getOpenAPI();
        if (openAPI == null) {
            throw new RuntimeException("Unable to parse the YAML specs on route with path \"" + routePath + "\":\n" +
                                       Arrays.toString(readContents.getMessages().toArray()));
        }

        Paths paths = openAPI.getPaths();

        return paths;
    }

    protected String getOpenApiPrefix() {
        return "openapi: 3.0.1";
    }

    protected boolean isToHideFromId(Route route) {
        return route.getId() != null && getRouteIdsToHide().contains(route.getId());
    }

    protected boolean isToHideFromHttpMethodAndPath(Route route, HttpMethod httpMethod) {

        String key = createHttpMethodAndPathKey(httpMethod, route.getPath());
        String keyAnyMethod = createHttpMethodAndPathKey(null, route.getPath());

        return getRouteHttpMethodAndPathToHide().contains(key) || getRouteHttpMethodAndPathToHide().contains(keyAnyMethod);
    }

    protected String createOperationId(Route route, HttpMethod httpMethod) {
        String operationId = route.getId();
        if (StringUtils.isBlank(operationId)) {
            operationId = httpMethod + route.getPath();
        }

        //==========================================
        // Will be used as a method name, can't start with a number.
        //==========================================
        operationId = "r" + DigestUtils.md5Hex(operationId);
        return operationId;
    }

    protected void addHttpMethodAnnotation(List handlerMethodAnnotations, HttpMethod httpMethod) {
        Annotation annotation = getHttpMethodAnnotation(httpMethod);
        handlerMethodAnnotations.add(annotation);
    }

    protected Annotation getHttpMethodAnnotation(HttpMethod httpMethod) {
        if (httpMethod == HttpMethod.GET) {
            return new GET() {

                @Override
                public Class annotationType() {
                    return GET.class;
                }
            };
        } else if (httpMethod == HttpMethod.DELETE) {
            return new DELETE() {

                @Override
                public Class annotationType() {
                    return DELETE.class;
                }
            };
        } else if (httpMethod == HttpMethod.HEAD) {
            return new HEAD() {

                @Override
                public Class annotationType() {
                    return HEAD.class;
                }
            };
        } else if (httpMethod == HttpMethod.OPTIONS) {
            return new OPTIONS() {

                @Override
                public Class annotationType() {
                    return OPTIONS.class;
                }
            };
        } else if (httpMethod == HttpMethod.PATCH) {
            return new PATCH() {

                @Override
                public Class annotationType() {
                    return PATCH.class;
                }
            };
        } else if (httpMethod == HttpMethod.POST) {
            return new POST() {

                @Override
                public Class annotationType() {
                    return POST.class;
                }
            };
        } else if (httpMethod == HttpMethod.PUT) {
            return new PUT() {

                @Override
                public Class annotationType() {
                    return PUT.class;
                }
            };
        } else {
            throw new RuntimeException("HTTP method not managed: " + httpMethod);
        }
    }

    protected void addPathAnnotationToHandlerMethod(List handlerMethodAannotations,
                                                    String routePath) {
        handlerMethodAannotations.add(new Path() {

            @Override
            public Class annotationType() {
                return Path.class;
            }

            @Override
            public String value() {
                String path = convertSpincastRoutePathToOpenApiFormat(routePath);
                return path;
            }
        });
    }

    /**
     * Convert dynamic path parameters to Open API format.
     * 

* /${param1}/${param3:\\d+}/${param4:<A>} *

* => *

* /{param1}/{param2}/{param3} */ protected String convertSpincastRoutePathToOpenApiFormat(String spincastRoutePath) { String path = spincastRoutePath.replaceAll("/(\\$\\{([^}:]+)(:([^}]+))?\\})", "/{$2}"); return path; } protected Specs getSpecsAnnotation(Object specsObj) { try { if (specsObj == null) { return null; } Class c = specsObj.getClass(); AnnotatedType[] annotatedInterfaces = c.getAnnotatedInterfaces(); Specs specsAnnotation = null; if (annotatedInterfaces != null && annotatedInterfaces.length > 0) { specsAnnotation = annotatedInterfaces[0].getAnnotation(Specs.class); } else { return null; } return specsAnnotation; } catch (Exception ex) { throw SpincastStatics.runtimize(ex); } } protected void addOperationAnnotationToHandlerMethod(List handlerMethodAnnotations, Specs specs) { if (specs == null) { return; } Operation operationAnnotation = specs.value(); if (operationAnnotation != null) { handlerMethodAnnotations.add(operationAnnotation); } } protected void addParametersAnnotationToHandlerMethod(List handlerMethodAnnotations, String routePath, Specs specs) { Operation operationAnnotation = specs != null ? specs.value() : null; Pattern pattern = Pattern.compile("/(\\$\\{([^}:]+)(:([^}]+))?\\})"); Matcher matcher = pattern.matcher(routePath); List parameterAnnotationsList = new ArrayList(); while (matcher.find()) { String paramName = matcher.group(2); String paramPattern = matcher.group(4); //========================================== // We add a @Parameter annotation // only if that parameter if not defined explicitly // on the @Operation annotation. //========================================== if (!isOperationAnnotationContainsParameter(operationAnnotation, paramName)) { String paramDescription = ""; if (paramPattern != null && paramPattern.startsWith("<") && paramPattern.endsWith(">")) { String alias = paramPattern.substring(1, paramPattern.length() - 1); paramDescription = createParamDescriptionFromAlias(alias); if (paramDescription == null) { paramDescription = paramPattern; } paramPattern = createParamPatternFromAlias(alias); } Parameter parameterAnnotation = getAnnotationsCreator().createPathParameterAnnotation(paramName, paramDescription, paramPattern); parameterAnnotationsList.add(parameterAnnotation); } } Parameters parameters = getAnnotationsCreator().createParametersAnnotation(parameterAnnotationsList); handlerMethodAnnotations.add(parameters); } protected String createParamDescriptionFromAlias(String alias) { if (getDefaultRouteParamAliasesBinder().getAlphaAliasKey().equals(alias)) { return "Alpha characters"; } if (getDefaultRouteParamAliasesBinder().geNumericAliasKey().equals(alias)) { return "Numeric characters"; } if (getDefaultRouteParamAliasesBinder().getAlphaPlusAliasKey().equals(alias)) { return "Alpha characters, \"_\" and \"-\""; } if (getDefaultRouteParamAliasesBinder().geNumericPlusAliasKey().equals(alias)) { return "Numeric characters, \"_\" and \"-\""; } if (getDefaultRouteParamAliasesBinder().getAlphaNumericAliasKey().equals(alias)) { return "Alphanumeric characters"; } if (getDefaultRouteParamAliasesBinder().getAlphaNumericPlusAliasKey().equals(alias)) { return "Alphanumeric characters, \"_\" and \"-\""; } return null; } protected String createParamPatternFromAlias(String alias) { Map routeParamPatternAliases = getRouter().getRouteParamPatternAliases(); String pattern = routeParamPatternAliases.get(alias); return pattern; } protected boolean isOperationAnnotationContainsParameter(Operation operationAnnotation, String paramName) { if (operationAnnotation == null || paramName == null || operationAnnotation.parameters() == null) { return false; } for (Parameter parameter : operationAnnotation.parameters()) { if (parameter != null && paramName.equals(parameter.name())) { return true; } } return false; } protected void addConsumesAnnotationToHandlerMethod(List handlerMethodAnnotations, Specs specs, Route route) { Consumes consumesAnnotation = null; if (specs != null && specs.consumes() != null && specs.consumes().value() != null && specs.consumes().value().length > 0 && !("".equals(specs.consumes().value()[0]))) { consumesAnnotation = specs.consumes(); } else { //========================================== // We use the "accept" content types of the // route, if there are some. //========================================== String[] acceptedContentTypes = null; Set acceptedContentTypesFromRoute = route.getAcceptedContentTypes(); if (acceptedContentTypesFromRoute != null && acceptedContentTypesFromRoute.size() > 0) { acceptedContentTypes = acceptedContentTypesFromRoute.toArray(new String[acceptedContentTypesFromRoute.size()]); } else { acceptedContentTypes = getSpincastOpenApiBottomUpPluginConfig().getDefaultConsumesContentTypes(); } final String[] acceptedContentTypesFinal = acceptedContentTypes; if (acceptedContentTypes != null && acceptedContentTypes.length > 0) { consumesAnnotation = new Consumes() { @Override public Class annotationType() { return Consumes.class; } @Override public String[] value() { return acceptedContentTypesFinal; } }; } } if (consumesAnnotation != null) { handlerMethodAnnotations.add(consumesAnnotation); } } protected void addProducesAnnotationToHandlerMethod(List handlerMethodAnnotations, Specs specs) { Produces producesAnnotation = null; if (specs != null && specs.produces() != null && specs.produces().value() != null && specs.produces().value().length > 0 && !("".equals(specs.produces().value()[0]))) { producesAnnotation = specs.produces(); } else { String[] defaultProducesContentTypes = getSpincastOpenApiBottomUpPluginConfig().getDefaultProducesContentTypes(); if (defaultProducesContentTypes != null && defaultProducesContentTypes.length > 0) { producesAnnotation = new Produces() { @Override public Class annotationType() { return Produces.class; } @Override public String[] value() { return defaultProducesContentTypes; } }; } } if (producesAnnotation != null) { handlerMethodAnnotations.add(producesAnnotation); } } @Override public String getOpenApiAsJson() { return getOpenApiAsJson(true); } @Override public String getOpenApiAsJson(boolean prettyFormatted) { String val = prettyFormatted ? this.openApiAsJsonPretty : this.openApiAsJson; if (val == null) { try { OpenAPI openAPI = getOpenApi(); if (prettyFormatted) { this.openApiAsJsonPretty = Json.pretty(openAPI); } else { this.openApiAsJson = Json.mapper() .writeValueAsString(openAPI); } } catch (Exception ex) { throw SpincastStatics.runtimize(ex); } } return prettyFormatted ? this.openApiAsJsonPretty : this.openApiAsJson; } @Override public String getOpenApiAsYaml() { return getOpenApiAsYaml(true); } @Override public String getOpenApiAsYaml(boolean prettyFormatted) { String val = prettyFormatted ? this.openApiAsYamlPretty : this.openApiAsYaml; if (val == null) { try { OpenAPI openAPI = getOpenApi(); if (prettyFormatted) { this.openApiAsYamlPretty = Yaml.pretty(openAPI); } else { this.openApiAsYaml = Yaml.mapper() .writeValueAsString(openAPI); } } catch (Exception ex) { throw SpincastStatics.runtimize(ex); } } return prettyFormatted ? this.openApiAsYamlPretty : this.openApiAsYaml; } @Override public void ignoreRoutesByIds(String... ids) { if (ids != null) { for (String id : ids) { getRouteIdsToHide().add(id); } } } protected String createHttpMethodAndPathKey(HttpMethod method, String path) { String key = ""; if (method != null) { key += method.name(); } key += " - " + path; return key; } @Override public void ignoreRouteUsingHttpMethodAndPath(HttpMethod method, String path) { String key = createHttpMethodAndPathKey(method, path); getRouteHttpMethodAndPathToHide().add(key); } protected void addYamlStringSpecifiedPaths(OpenAPI openApi) { List> mainRoutes = getRouter().getMainRoutes(); if (mainRoutes == null || mainRoutes.size() == 0) { return; } //========================================== // For each main route... //========================================== for (Route route : mainRoutes) { //========================================== // Hidden route? // From inline annotation or from id. //========================================== if (route.isSpecsIgnore() || isToHideFromId(route)) { logger.info("Route hidden from specs, won't be added:" + route); continue; } Object specs = route.getSpecs(); //========================================== // Paths specified as a YAML string //========================================== if (specs != null && specs instanceof String) { Paths pathsFromYamlString = getPathsFromYamlString(route.getPath(), (String)specs); if (pathsFromYamlString != null && pathsFromYamlString.size() > 0) { Paths paths = openApi.getPaths(); if (paths == null) { paths = new Paths(); openApi.setPaths(paths); } for (Entry entry : pathsFromYamlString.entrySet()) { String path = entry.getKey(); PathItem pathItem = entry.getValue(); if (pathItem != null) { Map operationsMap = pathItem.readOperationsMap(); if (operationsMap != null) { for (Entry entry2 : operationsMap.entrySet()) { //========================================== // Hidden route from HTTP method and path. //========================================== HttpMethod httpMethod = convertHttpMethodToSpincast(entry2.getKey()); if (isToHideFromHttpMethodAndPath(route, httpMethod)) { logger.info("Route hidden from specs, won't be added: [" + httpMethod.name() + "] " + route); if (httpMethod == HttpMethod.GET) { pathItem.setGet(null); } else if (httpMethod == HttpMethod.POST) { pathItem.setPost(null); } else if (httpMethod == HttpMethod.PUT) { pathItem.setPut(null); } else if (httpMethod == HttpMethod.PATCH) { pathItem.setPatch(null); } else if (httpMethod == HttpMethod.DELETE) { pathItem.setDelete(null); } else if (httpMethod == HttpMethod.HEAD) { pathItem.setHead(null); } else if (httpMethod == HttpMethod.OPTIONS) { pathItem.setOptions(null); } else if (httpMethod == HttpMethod.TRACE) { pathItem.setTrace(null); } } } } paths.addPathItem(path, pathItem); } } } } } } protected HttpMethod convertHttpMethodToSpincast(io.swagger.v3.oas.models.PathItem.HttpMethod httpMethod) { if (httpMethod == io.swagger.v3.oas.models.PathItem.HttpMethod.GET) { return HttpMethod.GET; } else if (httpMethod == io.swagger.v3.oas.models.PathItem.HttpMethod.POST) { return HttpMethod.POST; } else if (httpMethod == io.swagger.v3.oas.models.PathItem.HttpMethod.PUT) { return HttpMethod.PUT; } else if (httpMethod == io.swagger.v3.oas.models.PathItem.HttpMethod.PATCH) { return HttpMethod.PATCH; } else if (httpMethod == io.swagger.v3.oas.models.PathItem.HttpMethod.DELETE) { return HttpMethod.DELETE; } else if (httpMethod == io.swagger.v3.oas.models.PathItem.HttpMethod.HEAD) { return HttpMethod.HEAD; } else if (httpMethod == io.swagger.v3.oas.models.PathItem.HttpMethod.OPTIONS) { return HttpMethod.OPTIONS; } else if (httpMethod == io.swagger.v3.oas.models.PathItem.HttpMethod.TRACE) { return HttpMethod.TRACE; } throw new RuntimeException("HTTP method from Swagger not managed: " + httpMethod); } }