001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.support.swagger;
017
018import com.jfinal.core.ActionKey;
019import com.jfinal.core.Controller;
020import io.jboot.web.controller.JbootControllerManager;
021import io.swagger.models.Operation;
022import io.swagger.models.Path;
023import io.swagger.models.Response;
024import io.swagger.models.Swagger;
025import io.swagger.util.PathUtils;
026import io.swagger.util.ReflectionUtils;
027
028import java.lang.annotation.Annotation;
029import java.lang.reflect.Method;
030import java.lang.reflect.Type;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034
035/**
036 * 参考 : https://github.com/swagger-api/swagger-core 的 servlet 模块
037 */
038public class Reader {
039
040    private final Swagger swagger;
041
042    private Reader(Swagger swagger) {
043        this.swagger = swagger;
044    }
045
046    /**
047     * Scans a set of classes for Swagger annotations.
048     *
049     * @param swagger is the Swagger instance
050     * @param classes are a set of classes to scan
051     */
052    public static void read(Swagger swagger, List<Class> classes) {
053        final Reader reader = new Reader(swagger);
054        for (Class cls : classes) {
055            ReaderContext context = new ReaderContext(cls, "", null, false);
056            reader.read(context);
057        }
058    }
059
060    private void read(ReaderContext context) {
061
062        for (Method method : context.getCls().getDeclaredMethods()) {
063            if (ReflectionUtils.isOverriddenMethod(method, context.getCls())) {
064                continue;
065            }
066            final Operation operation = new Operation();
067
068
069            final Type[] genericParameterTypes = method.getGenericParameterTypes();
070            final Annotation[][] paramAnnotations = method.getParameterAnnotations();
071
072            ControllerReaderExtension extension = new ControllerReaderExtension();
073
074            String methodPath = "index".equals(method.getName()) ? "" : "/" + method.getName();
075            String operationPath = JbootControllerManager.me().getPathByController((Class<? extends Controller>) context.getCls()) + methodPath;
076            //如果有ActionKey注解的URL路径,则使用该路径而不是方法名
077            ActionKey actionKeyAnnotation = ReflectionUtils.getAnnotation(method, ActionKey.class);
078            if (actionKeyAnnotation != null && !actionKeyAnnotation.value().isEmpty()) {
079                if (actionKeyAnnotation.value().startsWith("./")) {
080                    String actionName = actionKeyAnnotation.value().substring(2);
081                    String pathByController = JbootControllerManager.me().getPathByController((Class<? extends Controller>) context.getCls());
082                    operationPath = pathByController.endsWith("/") ? (pathByController + actionName) : (pathByController + "/" + actionName);
083                } else {
084                    operationPath = actionKeyAnnotation.value();
085                }
086            }
087            String httpMethod = extension.getHttpMethod(context, method);
088
089            if (operationPath == null || httpMethod == null) {
090                continue;
091            }
092
093            if (extension.isReadable(context)) {
094                extension.setDeprecated(operation, method);
095                extension.applyConsumes(context, operation, method);
096                extension.applyProduces(context, operation, method);
097                extension.applyOperationId(operation, method);
098                extension.applySummary(operation, method);
099                extension.applyDescription(operation, method);
100                extension.applySchemes(context, operation, method);
101                extension.applySecurityRequirements(context, operation, method);
102                extension.applyTags(context, operation, method);
103                extension.applyResponses(swagger, context, operation, method);
104                extension.applyImplicitParameters(swagger, context, operation, method);
105                extension.applyExtensions(context, operation, method);
106                for (int i = 0; i < genericParameterTypes.length; i++) {
107                    extension.applyParameters(httpMethod, context, operation, paramAnnotations[i]);
108                }
109
110                if ("post".equalsIgnoreCase(httpMethod) && operation.getConsumes() == null) {
111                    operation.addConsumes("application/x-www-form-urlencoded");
112                }
113            }
114
115            if (operation.getResponses() == null) {
116                operation.defaultResponse(new Response().description("successful operation"));
117            }
118
119            final Map<String, String> regexMap = new HashMap<String, String>();
120            final String parsedPath = PathUtils.parsePath(operationPath, regexMap);
121
122            Path path = swagger.getPath(parsedPath);
123            if (path == null) {
124                path = new SwaggerPath();
125                swagger.path(parsedPath, path);
126            }
127            path.set(httpMethod.toLowerCase(), operation);
128        }
129    }
130
131
132}