Java tutorial
/* * 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()); } }