Finished OpenAPIWriter
This commit is contained in:
parent
02e91a38e9
commit
bb8d6a93b4
10 changed files with 215 additions and 107 deletions
|
|
@ -72,14 +72,14 @@ public class ClassUtil {
|
|||
* @return if the given class is a wrapper for a primitive
|
||||
*/
|
||||
public static boolean isWrapper(Class<?> type){
|
||||
return wrappers.contains( type );
|
||||
return wrappers.contains(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the given class is a primitive including String
|
||||
*/
|
||||
public static boolean isPrimitive(Class<?> type){
|
||||
return primitives.contains( type );
|
||||
return primitives.contains(type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -184,13 +184,26 @@ public class ClassUtil {
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param c a array class
|
||||
* @return the base class the array is based on, if the input is not an array then the input is returned.
|
||||
*/
|
||||
public static Class<?> getArrayClass(Class<?> c) {
|
||||
return getArrayClass(c, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param c a array class
|
||||
* @param depth the number of times the method should recurse into array. Value of zero will return the input value.
|
||||
* @return the base class the array is based on, if the input is not an array then the input is returned.
|
||||
*/
|
||||
public static Class<?> getArrayClass(Class<?> c, int depth) {
|
||||
if (depth <= 0) // Stop recursion
|
||||
return c;
|
||||
|
||||
if (c != null && c.isArray()) {
|
||||
return getArrayClass(c.getComponentType());
|
||||
return getArrayClass(c.getComponentType(), depth-1);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,13 +141,9 @@ public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface
|
|||
return ret;
|
||||
}
|
||||
public class BrowseRetObj extends WSReturnObject{
|
||||
@WSParamName("Result")
|
||||
public String Result;
|
||||
@WSParamName("NumberReturned")
|
||||
public int NumberReturned;
|
||||
@WSParamName("TotalMatches")
|
||||
public int TotalMatches;
|
||||
@WSParamName("UpdateID")
|
||||
public int UpdateID;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ public interface WSInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* This method will be used in the header.
|
||||
* This method will be used in the SOAP header.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
|
|
@ -146,7 +146,7 @@ public interface WSInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets a specific path for the method overriding the auto generated path.
|
||||
* Sets a specific URL path for the method overriding the auto generated path.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,10 @@ public class WSMethodDef {
|
|||
* A List of return parameters of the method
|
||||
**/
|
||||
private ArrayList<WSParameterDef> outputs;
|
||||
/**
|
||||
* The object type of output object
|
||||
**/
|
||||
private Class<?> outputClass;
|
||||
/**
|
||||
* A List of exceptions that this method throws
|
||||
**/
|
||||
|
|
@ -112,68 +116,58 @@ public class WSMethodDef {
|
|||
else
|
||||
namespace = wsDef.getNamespace() + "?#" + name;
|
||||
|
||||
// Hnadle Exceptions
|
||||
|
||||
Collections.addAll(exceptions, method.getExceptionTypes());
|
||||
|
||||
// Handle input parameter names
|
||||
// ------------------------------------------------
|
||||
// Handle inputs
|
||||
// ------------------------------------------------
|
||||
|
||||
Annotation[][] paramAnnotation = method.getParameterAnnotations();
|
||||
Class<?>[] inputTypes = method.getParameterTypes();
|
||||
|
||||
for (int i = 0; i < paramAnnotation.length; i++) {
|
||||
WSParameterDef param = new WSParameterDef(this);
|
||||
for (Annotation annotation : paramAnnotation[i]) {
|
||||
if (annotation instanceof WSInterface.WSParamName) {
|
||||
WSInterface.WSParamName paramName = (WSInterface.WSParamName) annotation;
|
||||
param.setName(paramName.value());
|
||||
param.setOptional(paramName.optional());
|
||||
}
|
||||
}
|
||||
param.setParamClass(inputTypes[i]);
|
||||
// if no name was found then use default
|
||||
WSParameterDef param = new WSParameterDef(this, inputTypes[i], paramAnnotation[i]);
|
||||
|
||||
// if no name was found then generate one
|
||||
if (param.getName() == null)
|
||||
param.setName("args" + i);
|
||||
|
||||
inputs.add(param);
|
||||
}
|
||||
|
||||
// Handle return parameter names
|
||||
// ------------------------------------------------
|
||||
// Handle outputs
|
||||
// ------------------------------------------------
|
||||
|
||||
WSInterface.WSReturnName returnNameAnnotation = method.getAnnotation(WSInterface.WSReturnName.class);
|
||||
if (WSReturnObject.class.isAssignableFrom(method.getReturnType())) {
|
||||
Class<?> retClass = method.getReturnType();
|
||||
Field[] fields = retClass.getFields();
|
||||
this.outputClass = method.getReturnType();
|
||||
if (WSReturnObject.class.isAssignableFrom(outputClass)) {
|
||||
Field[] fields = outputClass.getFields();
|
||||
|
||||
for (Field field : fields) {
|
||||
WSParameterDef ret_param = new WSParameterDef(this);
|
||||
WSParameterDef ret_param = new WSParameterDef(this, field.getType(), field.getAnnotations());
|
||||
|
||||
WSInterface.WSParamName paramNameAnnotation = field.getAnnotation(WSInterface.WSParamName.class);
|
||||
if (paramNameAnnotation != null)
|
||||
ret_param.setName(paramNameAnnotation.value());
|
||||
else
|
||||
if (ret_param.getName() == null)
|
||||
ret_param.setName(field.getName());
|
||||
|
||||
WSInterface.WSDocumentation documentationAnnotation = field.getAnnotation(WSInterface.WSDocumentation.class);
|
||||
if (documentationAnnotation != null)
|
||||
ret_param.setDocumentation(documentationAnnotation.value());
|
||||
|
||||
ret_param.setParamClass(field.getType());
|
||||
outputs.add(ret_param);
|
||||
}
|
||||
} else if (method.getReturnType() != void.class) {
|
||||
WSParameterDef ret_param = new WSParameterDef(this);
|
||||
} else if (outputClass != void.class) {
|
||||
WSInterface.WSReturnName returnNameAnnotation = method.getAnnotation(WSInterface.WSReturnName.class);
|
||||
WSParameterDef ret_param = new WSParameterDef(this, method.getReturnType(), new Annotation[]{returnNameAnnotation});
|
||||
|
||||
if (returnNameAnnotation != null)
|
||||
ret_param.setName(returnNameAnnotation.value());
|
||||
else
|
||||
if (ret_param.getName() == null)
|
||||
ret_param.setName("return");
|
||||
|
||||
ret_param.setParamClass(method.getReturnType());
|
||||
outputs.add(ret_param);
|
||||
}
|
||||
|
||||
// ------------------------------------------------
|
||||
// Handle Exceptions
|
||||
// ------------------------------------------------
|
||||
|
||||
Collections.addAll(exceptions, method.getExceptionTypes());
|
||||
|
||||
// ------------------------------------------------
|
||||
// Handle the request type
|
||||
// ------------------------------------------------
|
||||
|
||||
WSRequestType requestTypeAnnotation = method.getAnnotation(WSRequestType.class);
|
||||
if (requestTypeAnnotation != null) {
|
||||
|
|
@ -193,7 +187,9 @@ public class WSMethodDef {
|
|||
this.requestType = WSInterface.RequestType.GET;
|
||||
}
|
||||
|
||||
// ------------------------------------------------
|
||||
// Handle endpoint path
|
||||
// ------------------------------------------------
|
||||
|
||||
WSPath pathAnnotation = method.getAnnotation(WSPath.class);
|
||||
if (pathAnnotation != null)
|
||||
|
|
@ -240,6 +236,13 @@ public class WSMethodDef {
|
|||
return outputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the class of the output object
|
||||
*/
|
||||
public Class<?> getOutputClass() {
|
||||
return outputClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return documentation of the method if one exists or else null
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@
|
|||
|
||||
package zutil.net.ws;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* This is a web service parameter definition class
|
||||
*
|
||||
|
|
@ -39,21 +41,35 @@ public class WSParameterDef {
|
|||
/** Developer documentation **/
|
||||
private String documentation;
|
||||
/** If this parameter is optional **/
|
||||
private boolean optional;
|
||||
private boolean optional = false;
|
||||
|
||||
|
||||
protected WSParameterDef(WSMethodDef mDef){
|
||||
protected WSParameterDef(WSMethodDef mDef, Class<?> paramClass, Annotation[] annotations){
|
||||
this.mDef = mDef;
|
||||
this.optional = false;
|
||||
this.paramClass = paramClass;
|
||||
|
||||
for (Annotation annotation : annotations) {
|
||||
if (annotation == null)
|
||||
continue;
|
||||
|
||||
if (annotation instanceof WSInterface.WSParamName) {
|
||||
WSInterface.WSParamName paramNameAnnotation = (WSInterface.WSParamName) annotation;
|
||||
this.name = paramNameAnnotation.value();
|
||||
this.optional = paramNameAnnotation.optional();
|
||||
} else if (annotation instanceof WSInterface.WSReturnName) {
|
||||
WSInterface.WSReturnName returnAnnotation = (WSInterface.WSReturnName) annotation;
|
||||
this.name = returnAnnotation.value();
|
||||
} else if (annotation instanceof WSInterface.WSDocumentation) {
|
||||
WSInterface.WSDocumentation documentationAnnotation = (WSInterface.WSDocumentation) annotation;
|
||||
this.documentation = documentationAnnotation.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Class<?> getParamClass() {
|
||||
return paramClass;
|
||||
}
|
||||
protected void setParamClass(Class<?> paramClass) {
|
||||
this.paramClass = paramClass;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
|
|
|
|||
|
|
@ -24,10 +24,6 @@
|
|||
|
||||
package zutil.net.ws;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
package zutil.net.ws.openapi;
|
||||
|
||||
import zutil.ClassUtil;
|
||||
import zutil.log.LogUtil;
|
||||
import zutil.net.ws.WSMethodDef;
|
||||
import zutil.net.ws.WSParameterDef;
|
||||
import zutil.net.ws.WebServiceDef;
|
||||
import zutil.net.ws.*;
|
||||
import zutil.parser.DataNode;
|
||||
import zutil.parser.json.JSONWriter;
|
||||
|
||||
import javax.xml.crypto.Data;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
|
|
@ -22,6 +20,7 @@ import java.util.logging.Logger;
|
|||
*
|
||||
* @see <a href="https://swagger.io/specification/">OpenAPI Specification</a>
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class OpenAPIWriter {
|
||||
private static final Logger logger = LogUtil.getLogger();
|
||||
|
||||
|
|
@ -60,14 +59,14 @@ public class OpenAPIWriter {
|
|||
|
||||
public String write() {
|
||||
if (cache == null) {
|
||||
Map<String, List<WSParameterDef>> schemas = new HashMap<>();
|
||||
List<Class> objSchemas = new ArrayList<>();
|
||||
|
||||
DataNode root = new DataNode(DataNode.DataType.Map);
|
||||
root.set("openapi", OPENAPI_VERSION);
|
||||
root.set("info", generateInfo());
|
||||
root.set("servers", generateServers());
|
||||
root.set("paths", generatePaths(schemas));
|
||||
root.set("components", generateComponents(schemas));
|
||||
root.set("paths", generatePaths(objSchemas));
|
||||
root.set("components", generateComponents(objSchemas));
|
||||
|
||||
this.cache = JSONWriter.toString(root);
|
||||
}
|
||||
|
|
@ -77,9 +76,11 @@ public class OpenAPIWriter {
|
|||
private DataNode generateInfo() {
|
||||
DataNode infoRoot = new DataNode(DataNode.DataType.Map);
|
||||
infoRoot.set("title", ws.getName());
|
||||
infoRoot.set("description", ws.getDocumentation());
|
||||
infoRoot.set("version", "");
|
||||
|
||||
if (ws.getDocumentation() != null)
|
||||
infoRoot.set("description", ws.getDocumentation());
|
||||
|
||||
// Not implemented properties
|
||||
// "termsOfService": xxx,
|
||||
// "contact": {"name": xxx,"url": xxx,"email": xxx},
|
||||
|
|
@ -99,27 +100,33 @@ public class OpenAPIWriter {
|
|||
return serversRoot;
|
||||
}
|
||||
|
||||
private DataNode generatePaths(Map<String, List<WSParameterDef>> schemas) {
|
||||
private DataNode generatePaths(List<Class> objSchemas) {
|
||||
DataNode pathsRoot = new DataNode(DataNode.DataType.Map);
|
||||
|
||||
for (WSMethodDef methodDef : ws.getMethods()) {
|
||||
DataNode pathNode = pathsRoot.set(methodDef.getPath(), DataNode.DataType.Map);
|
||||
|
||||
DataNode typeNode = pathNode.set(methodDef.getRequestType().toString().toLowerCase(), DataNode.DataType.Map);
|
||||
typeNode.set("description", methodDef.getDocumentation());
|
||||
|
||||
if (methodDef.getDocumentation() != null)
|
||||
typeNode.set("description", methodDef.getDocumentation());
|
||||
|
||||
// --------------------------------------------
|
||||
// Inputs
|
||||
// --------------------------------------------
|
||||
|
||||
DataNode parameterNode = typeNode.set("parameters", DataNode.DataType.Map);
|
||||
DataNode parametersNode = typeNode.set("parameters", DataNode.DataType.List);
|
||||
for (WSParameterDef parameterDef : methodDef.getInputs()) {
|
||||
DataNode parameterNode = parametersNode.add(DataNode.DataType.Map);
|
||||
parameterNode.set("name", parameterDef.getName());
|
||||
parameterNode.set("description", parameterDef.getDocumentation());
|
||||
parameterNode.set("in", "query");
|
||||
parameterNode.set("required", parameterDef.isOptional());
|
||||
parameterNode.set("required", !parameterDef.isOptional());
|
||||
|
||||
parameterNode.set("schema", "");
|
||||
if (parameterDef.getDocumentation() != null)
|
||||
parameterNode.set("description", parameterDef.getDocumentation());
|
||||
|
||||
DataNode schemaNode = parameterNode.set("schema", DataNode.DataType.Map);
|
||||
generateSchema(schemaNode, parameterDef.getParamClass(), true, objSchemas);
|
||||
}
|
||||
|
||||
// --------------------------------------------
|
||||
|
|
@ -127,31 +134,104 @@ public class OpenAPIWriter {
|
|||
// --------------------------------------------
|
||||
|
||||
DataNode responseNode = typeNode.set("responses", DataNode.DataType.Map);
|
||||
DataNode schemaNode = responseNode.set("200", DataNode.DataType.Map)
|
||||
.set("content", DataNode.DataType.Map)
|
||||
.set("application/json", DataNode.DataType.Map)
|
||||
.set("schema", DataNode.DataType.Map);
|
||||
|
||||
String retName = methodDef.getName() + "Return";
|
||||
schemas.put("retName", methodDef.getOutputs());
|
||||
schemaNode.set("$ref", "#/components/schemas/" + retName);
|
||||
DataNode successNode = responseNode.set("200", DataNode.DataType.Map);
|
||||
successNode.set("description", "A successful response.");
|
||||
|
||||
if (methodDef.getOutputClass() != void.class) {
|
||||
DataNode schemaNode = successNode.set("content", DataNode.DataType.Map)
|
||||
.set("application/json", DataNode.DataType.Map)
|
||||
.set("schema", DataNode.DataType.Map);
|
||||
generateSchema(schemaNode, methodDef.getOutputClass(), true, objSchemas);
|
||||
}
|
||||
}
|
||||
|
||||
return pathsRoot;
|
||||
}
|
||||
|
||||
private DataNode generateComponents(Map<String, List<WSParameterDef>> schemas) {
|
||||
private DataNode generateComponents(List<Class> objSchemas) {
|
||||
DataNode componentsRoot = new DataNode(DataNode.DataType.Map);
|
||||
DataNode schemasNode = new DataNode(DataNode.DataType.Map);
|
||||
componentsRoot.set("schemas", schemasNode);
|
||||
DataNode schemasNode = componentsRoot.set("schemas", DataNode.DataType.Map);
|
||||
|
||||
// Generate schemas
|
||||
|
||||
for (int i=0; i<objSchemas.size(); i++) {
|
||||
Class clazz = objSchemas.get(i);
|
||||
|
||||
DataNode objectNode = schemasNode.set(clazz.getSimpleName(), DataNode.DataType.Map);
|
||||
generateSchema(objectNode, clazz, false, objSchemas);
|
||||
}
|
||||
|
||||
return componentsRoot;
|
||||
}
|
||||
|
||||
private void generateSchema(DataNode parent, Class<?> clazz, boolean reference, List<Class> objSchemas) {
|
||||
if (clazz == void.class)
|
||||
return;
|
||||
|
||||
if (ClassUtil.isPrimitive(clazz) || ClassUtil.isWrapper(clazz)) {
|
||||
parent.set("type", getOpenAPIType(clazz));
|
||||
|
||||
if (clazz == byte.class || clazz == Byte.class)
|
||||
parent.set("format", "byte");
|
||||
} else if (clazz.isArray() || Collection.class.isAssignableFrom(clazz)) {
|
||||
parent.set("type", "array");
|
||||
|
||||
DataNode itemsNode = parent.set("items", DataNode.DataType.Map);
|
||||
generateSchema(itemsNode, ClassUtil.getArrayClass(clazz, 1), reference, objSchemas);
|
||||
} else {
|
||||
parent.set("type", "object");
|
||||
|
||||
if (reference) {
|
||||
if (!objSchemas.contains(clazz))
|
||||
objSchemas.add(clazz);
|
||||
parent.set("$ref", "#/components/schemas/" + clazz.getSimpleName());
|
||||
} else {
|
||||
if (WSReturnObject.class.isAssignableFrom(clazz)) {
|
||||
DataNode propertiesNode = parent.set("properties", DataNode.DataType.Map);
|
||||
for (Field field : clazz.getFields()) {
|
||||
String fieldName = field.getName();
|
||||
|
||||
WSInterface.WSParamName paramNameAnnotation = field.getAnnotation(WSInterface.WSParamName.class);
|
||||
if (paramNameAnnotation != null)
|
||||
fieldName = paramNameAnnotation.value();
|
||||
|
||||
DataNode parameterNode = propertiesNode.set(fieldName, DataNode.DataType.Map);
|
||||
generateSchema(parameterNode, field.getType(), false, objSchemas);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getOpenAPIType(Class<?> clazz) {
|
||||
switch (clazz.getName()) {
|
||||
case "int":
|
||||
case "java.lang.Integer":
|
||||
return "integer";
|
||||
|
||||
case "float":
|
||||
case "java.lang.Float":
|
||||
case "double":
|
||||
case "java.lang.Double":
|
||||
return "number";
|
||||
|
||||
case "byte":
|
||||
case "java.lang.Byte":
|
||||
case "java.lang.String":
|
||||
return "string";
|
||||
|
||||
case "boolean":
|
||||
case "java.lang.Boolean":
|
||||
return "boolean";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class containing Target API server information.
|
||||
*/
|
||||
protected static class ServerData {
|
||||
String url;
|
||||
String description;
|
||||
|
|
|
|||
|
|
@ -373,6 +373,7 @@ public class SOAPHttpPage implements HttpPage{
|
|||
*/
|
||||
public static String getSOAPClassName(Class<?> c) {
|
||||
Class<?> cTmp = ClassUtil.getArrayClass(c);
|
||||
|
||||
if (byte[].class.isAssignableFrom(c)) {
|
||||
return "base64Binary";
|
||||
} else if (WSReturnObject.class.isAssignableFrom(cTmp)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue