com.cpjit.swagger4j.APIParser.java Source code

Java tutorial

Introduction

Here is the source code for com.cpjit.swagger4j.APIParser.java

Source

/*
 * Copyright 2011-2017 CPJIT Group.
 * 
 * 
 * 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.cpjit.swagger4j;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONWriter;
import com.cpjit.swagger4j.annotation.API;
import com.cpjit.swagger4j.annotation.APISchema;
import com.cpjit.swagger4j.annotation.APISchemaPropertie;
import com.cpjit.swagger4j.annotation.APISchemas;
import com.cpjit.swagger4j.annotation.APITag;
import com.cpjit.swagger4j.annotation.APITags;
import com.cpjit.swagger4j.annotation.APIs;
import com.cpjit.swagger4j.annotation.DataType;
import com.cpjit.swagger4j.annotation.Item;
import com.cpjit.swagger4j.annotation.Items;
import com.cpjit.swagger4j.annotation.Param;
import com.cpjit.swagger4j.util.ReflectUtils;

/**
 * ??
 * 
 * <p>?????</p>
 * 
 * <pre>
 *    // 
 *    String host = "127.0.0.1/app";
 *    String file = "c:/apis.json";
 *    String[] packageToScan = new String[]{"com.cpj.demo.api"};
 *    APIParser.Builder builder = new APIParser.Builder(host, file, packageToScan);
 * 
 *   // ??
 *    builder.basePath("/");
 * 
 *    // ?
 *    APIParser parser = builder.build();
 *    // ?
 *    parser.parse();
 * </pre>
 * <p>?????</p>
 * <pre>
 *    APIParser.newInstance(props);
 * </pre>
 * @author yonghaun
 * @since 1.0.0
 */
public final class APIParser implements APIParseable {

    private final static Logger LOG = LoggerFactory.getLogger(APIParser.class);
    private final static String DELIMITER = "[;,]";
    private String host;
    private String basePath;
    private String suffix = "";
    private APIDocInfo info;
    private String file;
    private List<String> packageToScan;
    private Map<String, Item> items;

    /**
     * ?
     * @param props properties
     * @throws IOException
     * @see APIParser.Builder
     */
    public final static APIParser newInstance(Properties props) throws IOException {
        String[] packageToScan = props.getProperty("packageToScan").split(DELIMITER);
        Builder builder = new Builder(props.getProperty("apiHost"), props.getProperty("apiFile"), packageToScan)
                .basePath(props.getProperty("apiBasePath")).description(props.getProperty("apiDescription"))
                .termsOfService(props.getProperty("termsOfService")).title(props.getProperty("apiTitle"))
                .version(props.getProperty("apiVersion")).suffix(props.getProperty("suffix"));
        return new APIParser(builder);
    }

    private APIParser(Builder builder) {
        // ??class???
        this.host = builder.host;
        this.file = builder.file;
        this.packageToScan = builder.packageToScan;
        try {
            packages = ReflectUtils.scanPackages(this.packageToScan, true);
        } catch (Exception e) {
            throw new IllegalStateException("???", e);
        }
        this.basePath = builder.basePath;
        this.suffix = builder.suffix;

        // API?
        info = new APIDocInfo.Builder().contact(builder.contact).description(builder.description)
                .license(builder.license).termsOfService(builder.termsOfService).title(builder.title)
                .version(builder.version).build();
    }

    /**
     * @author yonghuan
     */
    public static class Builder {
        // required args
        private String host;
        private String file;
        private List<String> packageToScan;

        private String basePath;
        private String suffix = "";
        private String description;
        private String version;
        private String title;
        private String termsOfService;
        private String contact;
        private License license;

        /**
         * 
         * 
         * @param host
         *            API?????
         * @param file
         *            ?
         * @param packageToScan
         *            ??
         */
        public Builder(String host, String file, String[] packageToScan) {
            this(host, file, Arrays.asList(packageToScan));
        }

        /**
         * 
         * 
         * @param host
         *            API?????
         * @param file
         *            ?
         * @param packageToScan
         *            ??
         */
        public Builder(String host, String file, List<String> packageToScan) {
            this.host = host;
            this.file = file;
            this.packageToScan = packageToScan;
        }

        /**
         * ?
         */
        public APIParser build() {
            return new APIParser(this);
        }

        /**
         * APIhostAPI?
         * 
         * @param val
         *            APIhostAPI?
         */
        public Builder basePath(String val) {
            this.basePath = val;
            return this;
        }

        /**
         * ??.do?.action
         * @param suffix ??
         */
        public Builder suffix(String suffix) {
            if (StringUtils.isNotBlank(suffix)) {
                this.suffix = suffix;
            }
            return this;
        }

        /**
         * API??
         * @param val
         *            API??
         */
        public Builder description(String val) {
            this.description = val;
            return this;
        }

        /**
         * API
         * 
         * @param val
         *            API
         */
        public Builder version(String val) {
            this.version = val;
            return this;
        }

        /**
         * API
         * 
         * @param val
         *            API
         */
        public Builder title(String val) {
            this.title = val;
            return this;
        }

        /**
         * API???
         * 
         * @param val
         *            API???
         */
        public Builder termsOfService(String val) {
            this.termsOfService = val;
            return this;
        }

        /**
         * API??
         * 
         * @param val
         *            API??
         */
        public Builder contact(String val) {
            this.contact = val;
            return this;
        }

        /**
         * API???apahce???
         * 
         * @param val
         *            API???apahce???
         */
        public Builder license(License val) {
            this.license = val;
            return this;
        }
    }

    /**
     * @return ???JSON?
     */
    public String getFile() {
        return file;
    }

    /**
     * @return ??
     */
    public List<String> getPackageToScan() {
        return packageToScan;
    }

    private List<Package> packages;

    @Override
    public void parse() throws Exception {
        /*  */
        File f = new File(file);
        if (LOG.isDebugEnabled()) {
            LOG.debug(String.join("", "??=>", f.getAbsolutePath()));
        }
        JSONWriter writer = new JSONWriter(new FileWriter(f));
        APIDoc api = (APIDoc) parseAndNotStore();
        writer.writeObject(api);
        writer.flush();
        writer.close();
    }

    @Override
    public Object parseAndNotStore() throws Exception {
        APIDoc api = new APIDoc();
        String[] schemes = new String[] { "http" };
        api.setSchemes(schemes);
        api.setHost(host);
        api.setBasePath(basePath);
        api.setInfo(info);

        /* ?item */
        items = parseItem();

        /* ?tag */
        Collection<Tag> tags = parseTag();
        api.setTags(tags);

        /* ?path */
        Map<String, Map<String, Path>> paths = parsePath();
        api.setPaths(paths);

        /* ?definition */
        Map<String, Object> definitions = parseDefinition();
        api.setDefinitions(definitions);
        return api;
    }

    private Map<String, Item> parseItem() throws Exception {
        return packages.stream().filter(pk -> pk.getAnnotation(Items.class) != null)
                .map(pk -> pk.getAnnotation(Items.class)).flatMap(items -> Arrays.stream(items.items()))
                .collect(Collectors.toMap(item -> item.value(), item -> item));
    }

    /**
     * url -> [ path ]
     */
    private Map<String, Map<String, Path>> parsePath() throws Exception {
        Map<String, Map<String, Path>> paths = new HashMap<String, Map<String, Path>>();
        //      List<Class<?>> clazzs = ReflectUtils.scanClazzs(packageToScan, true); // ???
        //      for(Class<?> clazz : clazzs) {
        //         APIs apis = clazz.getAnnotation(APIs.class);
        //         if(apis == null || apis.hide()) {
        //            continue;
        //         }
        //         scanAPIMethod(clazz).stream()
        //                        .forEachOrdered(method -> api2Path(method, apis, paths));
        //      }
        ReflectUtils.scanClazzs(packageToScan, true).stream().forEach(clazz -> {
            APIs apis = clazz.getAnnotation(APIs.class);
            if (apis == null || apis.hide()) {
                return;
            }
            scanAPIMethod(clazz).stream().forEach(method -> api2Path(method, apis, paths));
        });
        return paths;
    }

    private void api2Path(Method method, APIs apis, Map<String, Map<String, Path>> paths) {
        API service = method.getAnnotation(API.class);
        if (service.hide()) {
            return;
        }
        final boolean isMultipart = hasMultipart(service);
        String url;
        if ("".equals(service.value())) {
            url = String.join("", apis.value(), suffix);
        } else {
            url = String.join("", apis.value(), "/", service.value(), suffix);
        }
        Map<String, Path> path = paths.get(url); // get/psot/put/delete
        if (path == null) {
            path = new HashMap<String, Path>();
            paths.put(url, path);
        }

        Path p = path.get(service.method());
        if (p == null) {
            p = new Path();
            path.put(service.method().toLowerCase(), p);
        }
        if (StringUtils.isNotBlank(service.description())) {
            p.setDescription(service.description());
        } else {
            p.setDescription(service.summary());
        }
        if (StringUtils.isNotBlank(service.operationId())) {
            p.setOperationId(service.operationId());
        } else { // operationId
            p.setOperationId(method.getName());
        }
        List<String> tags = Arrays.asList(service.tags());
        if (service.tags().length == 0) {
            String ns = apis.value();
            if (ns.startsWith("/")) {
                ns = ns.substring(1);
            }
            tags = Arrays.asList(ns);
        }
        p.setTags(tags);
        p.setSummary(service.summary());
        if (isMultipart) { // multipart/form-data
            p.setConsumes(Arrays.asList("multipart/form-data"));
        } else {
            p.setConsumes(Arrays.asList(service.consumes()));
        }
        p.setProduces(Arrays.asList(service.produces()));
        p.setDeprecated(service.deprecated());
        p.setParameters(parseParameters(service, isMultipart));
    }

    private List<Map<String, Object>> parseParameters(API service, boolean isMultipart) {
        List<Map<String, Object>> parameters = new ArrayList<Map<String, Object>>(); // ?
        /** ??schema */
        for (Param paramAttr : service.parameters()) {
            Map<String, Object> parameter = new HashMap<String, Object>();
            if (paramAttr.schema() != null && !paramAttr.schema().trim().equals("")) { // ????
                if (isMultipart) { // Content-Typemultipart/form-data???
                    throw new IllegalArgumentException(String.join("",
                            "Content-Typemultipart/form-data???[ ",
                            paramAttr.schema(), " ]"));
                }
                parameter.put("in", "body");
                parameter.put("name", "body");
                Map<String, Object> ref = new HashMap<String, Object>();
                ref.put("$ref", "#/definitions/" + paramAttr.schema());
                parameter.put("schema", ref);
            } else { // ??
                String requestParamType, requestParamFormat;
                if (paramAttr.dataType() != DataType.UNKNOWN) { // since 1.2.2
                    requestParamType = paramAttr.dataType().type();
                    requestParamFormat = paramAttr.dataType().format();
                } else {
                    requestParamType = paramAttr.type();
                    requestParamFormat = paramAttr.format();
                }
                if (isMultipart && !"path".equals(paramAttr.in()) && !"header".equals(paramAttr.in())) { // ?
                    parameter.put("in", "formData");
                    parameter.put("type", requestParamType);
                } else { // ??
                    String in = paramAttr.in();
                    if (StringUtils.isBlank(in)) {
                        if ("post".equalsIgnoreCase(service.method())) {
                            in = "formData";
                        } else {
                            in = "query";
                        }
                    }
                    parameter.put("in", in);
                    parameter.put("type", requestParamType);
                    if (StringUtils.isNotBlank(requestParamFormat)) {
                        parameter.put("format", requestParamFormat);
                    }
                }
                parameter.put("name", paramAttr.name());
                parameter.put("description", paramAttr.description());
                parameter.put("required", paramAttr.required());
                if (paramAttr.items() != null && !paramAttr.items().trim().equals("")) {
                    if (!requestParamType.equals("array")) {
                        throw new IllegalArgumentException(String.join("", "? [ ", paramAttr.name(),
                                " ]?(items)?(type)?array"));
                    }
                    Item item = items.get(paramAttr.items().trim());
                    if (item != null) { // ?
                        Map<String, Object> i = new HashMap<String, Object>();
                        i.put("type", item.type());
                        i.put("default", item.defaultValue());
                        i.put("enum", parseOptionalValue(item.type(), item.optionalValue()));
                        parameter.put("items", i);
                    }
                }
            }
            if (StringUtils.isNotBlank(paramAttr.defaultValue())) {
                parameter.put("defaultValue", paramAttr.defaultValue());
            }
            parameters.add(parameter);
        }
        return parameters;
    }

    private Object parseOptionalValue(String type, String[] values) {
        if (type.equals("string")) { // string
            return values;
        } else if (type.equals("boolean")) { // boolean
            return Arrays.stream(values).map(s -> Boolean.parseBoolean(s)).collect(Collectors.toList());
        } else if (type.equals("integer")) { // integer
            return Arrays.stream(values).mapToInt(s -> Integer.parseInt(s)).toArray();
        } else { // double
            return Arrays.stream(values).mapToDouble(s -> Double.parseDouble(s)).toArray();
        }
    }

    /**
     * ?Content-Type?multipart/form-data
     */
    private boolean hasMultipart(API service) {
        return Arrays.stream(service.consumes()).filter(consume -> "multipart/form-data".equals(consume))
                .findFirst().isPresent();
    }

    /**
     * ?Tag
     * @return Tag
     */
    private Collection<Tag> parseTag() throws Exception {
        // since1.2.2 ??@APITag
        Stream<APITag> tagsFromClass = scanAPIsAnnotations().stream();
        // ??package-info?@APITags
        Stream<APITag> tagsFromPackage = packages.stream().map(pk -> pk.getAnnotation(APITags.class))
                .filter(apiTags -> apiTags != null).flatMap(apiTags -> Arrays.stream(apiTags.tags()));
        return Stream.of(tagsFromClass, tagsFromPackage).flatMap(stream -> stream)
                .map(apiTag -> new Tag(apiTag.value(), apiTag.description())).collect(Collectors.toSet());
    }

    /**
     * ?definition
     * 
     * @return definition
     * @throws Exception
     */
    private Map<String, Object> parseDefinition() throws Exception {
        Map<String, Object> definitions = new HashMap<String, Object>();

        for (Package pk : packages) {
            APISchemas apiSchemas = pk.getAnnotation(APISchemas.class);
            if (apiSchemas == null) {
                continue;
            }
            APISchema[] schemas = apiSchemas.schemas();
            for (APISchema schema : schemas) {
                Map<String, Object> definition = new HashMap<String, Object>();
                definition.put("type", schema.type());
                List<String> required = new ArrayList<String>();
                definition.put("required", required);
                APISchemaPropertie[] props = schema.properties();
                Map<String, Map<String, Object>> properties = new HashMap<String, Map<String, Object>>();
                for (APISchemaPropertie prop : props) {
                    Map<String, Object> propertie = new HashMap<String, Object>();
                    definition.put("properties", properties);

                    propertie.put("type", prop.type());
                    propertie.put("format", prop.format());
                    propertie.put("description", prop.description());

                    if (prop.required()) { // ?
                        required.add(prop.value());
                    }
                    if (prop.optionalValue().length > 0) { // ?
                        propertie.put("enum", parseOptionalValue(prop.type(), prop.optionalValue()));
                    }
                    properties.put(prop.value(), propertie);
                }
                definitions.put(schema.value(), definition); // definition
            }
        }
        return definitions;
    }

    /*
     * ??{@link API}
     * 
     * @return {@link API}
     * @throws Exception
     */
    private List<Method> scanAPIMethod(Class<?> clazz) {
        APIs apis = clazz.getAnnotation(APIs.class);
        if (apis == null) {
            return Collections.emptyList();
        }
        return Arrays.stream(clazz.getDeclaredMethods()).filter(method -> method.getAnnotation(API.class) != null)
                .collect(Collectors.toList());
    }

    /*
     * ???@APITag
     * @since 1.2.2
     */
    private List<APITag> scanAPIsAnnotations() throws Exception {
        return ReflectUtils.scanClazzs(packageToScan, true).stream().map(clazz -> clazz.getAnnotation(APITag.class))
                .filter(apiTag -> apiTag != null).collect(Collectors.toList());
    }
}