From 6d6fbf93ca3e6f3e91c546769992eafd9d2182cd Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Fri, 17 May 2024 00:41:40 +0200 Subject: [PATCH] [DEV] refacto the external rest api generator --- .../dataAccess/DataFactoryTsApi.java | 267 ++++-------------- .../archidata/externalRestApi/AnalyzeApi.java | 5 + .../externalRestApi/AnalyzeModel.java | 5 + .../externalRestApi/GeneratePythonApi.java | 5 + .../externalRestApi/GeneratePythonModel.java | 5 + .../externalRestApi/GenerateTsApi.java | 5 + .../externalRestApi/GenerateTsModel.java | 5 + .../externalRestApi/model/ApiGroupModel.java | 60 ++++ .../externalRestApi/model/ApiModel.java | 239 ++++++++++++++++ .../externalRestApi/model/ApiTool.java | 233 +++++++++++++++ .../externalRestApi/model/ClassEnumModel.java | 8 + .../externalRestApi/model/ClassListModel.java | 19 ++ .../externalRestApi/model/ClassMapModel.java | 20 ++ .../externalRestApi/model/ClassModel.java | 35 +++ .../model/ClassObjectModel.java | 8 + .../externalRestApi/model/ModelGroup.java | 22 ++ .../model/RestTypeRequest.java | 5 + 17 files changed, 728 insertions(+), 218 deletions(-) create mode 100644 src/org/kar/archidata/externalRestApi/AnalyzeApi.java create mode 100644 src/org/kar/archidata/externalRestApi/AnalyzeModel.java create mode 100644 src/org/kar/archidata/externalRestApi/GeneratePythonApi.java create mode 100644 src/org/kar/archidata/externalRestApi/GeneratePythonModel.java create mode 100644 src/org/kar/archidata/externalRestApi/GenerateTsApi.java create mode 100644 src/org/kar/archidata/externalRestApi/GenerateTsModel.java create mode 100644 src/org/kar/archidata/externalRestApi/model/ApiGroupModel.java create mode 100644 src/org/kar/archidata/externalRestApi/model/ApiModel.java create mode 100644 src/org/kar/archidata/externalRestApi/model/ApiTool.java create mode 100644 src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java create mode 100644 src/org/kar/archidata/externalRestApi/model/ClassListModel.java create mode 100644 src/org/kar/archidata/externalRestApi/model/ClassMapModel.java create mode 100644 src/org/kar/archidata/externalRestApi/model/ClassModel.java create mode 100644 src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java create mode 100644 src/org/kar/archidata/externalRestApi/model/ModelGroup.java create mode 100644 src/org/kar/archidata/externalRestApi/model/RestTypeRequest.java diff --git a/src/org/kar/archidata/dataAccess/DataFactoryTsApi.java b/src/org/kar/archidata/dataAccess/DataFactoryTsApi.java index 88d6ed2..8c3ca6d 100644 --- a/src/org/kar/archidata/dataAccess/DataFactoryTsApi.java +++ b/src/org/kar/archidata/dataAccess/DataFactoryTsApi.java @@ -5,12 +5,11 @@ import java.io.File; import java.io.FileWriter; import java.io.InputStream; import java.io.InputStreamReader; -import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -20,37 +19,23 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import org.glassfish.jersey.media.multipart.FormDataParam; -import org.kar.archidata.annotation.AsyncType; -import org.kar.archidata.annotation.TypeScriptProgress; import org.kar.archidata.catcher.RestErrorResponse; import org.kar.archidata.dataAccess.DataFactoryZod.ClassElement; import org.kar.archidata.dataAccess.DataFactoryZod.GeneratedTypes; +import org.kar.archidata.externalRestApi.model.ApiTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.swagger.v3.oas.annotations.Operation; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.PATCH; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; public class DataFactoryTsApi { static final Logger LOGGER = LoggerFactory.getLogger(DataFactoryTsApi.class); - + record APIModel( String data, String className) {} - + /** Request the generation of the TypeScript file for the "Zod" export model * @param classs List of class used in the model * @throws Exception */ @@ -74,7 +59,7 @@ public class DataFactoryTsApi { RESTRequestVoid } from "./rest-tools" import {"""; - + for (final Class clazz : classs) { final Set> includeModel = new HashSet<>(); final Set> includeCheckerModel = new HashSet<>(); @@ -112,7 +97,7 @@ public class DataFactoryTsApi { } generatedData.append("\n} from \"./model\"\n"); generatedData.append(api.data()); - + String fileName = api.className(); fileName = fileName.replaceAll("([A-Z])", "-$1").toLowerCase(); fileName = fileName.replaceAll("^\\-*", ""); @@ -123,179 +108,11 @@ public class DataFactoryTsApi { } return apis; } - - public static String apiAnnotationGetPath(final Class element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(Path.class); - if (annotation.length == 0) { - return null; - } - return ((Path) annotation[0]).value(); - } - - public static List apiAnnotationProduces(final Class element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(Produces.class); - if (annotation.length == 0) { - return null; - } - return Arrays.asList(((Produces) annotation[0]).value()); - } - - public static List apiAnnotationProduces(final Method element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(Produces.class); - if (annotation.length == 0) { - return null; - } - return Arrays.asList(((Produces) annotation[0]).value()); - } - - public static boolean apiAnnotationTypeScriptProgress(final Method element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(TypeScriptProgress.class); - if (annotation.length == 0) { - return false; - } - return true; - } - - public static List apiAnnotationProduces(final Class clazz, final Method method) throws Exception { - final List data = apiAnnotationProduces(method); - if (data != null) { - return data; - } - return apiAnnotationProduces(clazz); - } - - public static String apiAnnotationGetOperationDescription(final Method element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(Operation.class); - if (annotation.length == 0) { - return null; - } - return ((Operation) annotation[0]).description(); - } - - public static String apiAnnotationGetPath(final Method element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(Path.class); - if (annotation.length == 0) { - return null; - } - return ((Path) annotation[0]).value(); - } - - public static String apiAnnotationGetTypeRequest(final Method element) throws Exception { - if (element.getDeclaredAnnotationsByType(GET.class).length == 1) { - return "GET"; - } - if (element.getDeclaredAnnotationsByType(POST.class).length == 1) { - return "POST"; - } - if (element.getDeclaredAnnotationsByType(PUT.class).length == 1) { - return "PUT"; - } - if (element.getDeclaredAnnotationsByType(PATCH.class).length == 1) { - return "PATCH"; - } - if (element.getDeclaredAnnotationsByType(DELETE.class).length == 1) { - return "DELETE"; - } - return null; - } - - public static String apiAnnotationGetPathParam(final Parameter element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(PathParam.class); - if (annotation.length == 0) { - return null; - } - return ((PathParam) annotation[0]).value(); - } - - public static String apiAnnotationGetQueryParam(final Parameter element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(QueryParam.class); - if (annotation.length == 0) { - return null; - } - return ((QueryParam) annotation[0]).value(); - } - - public static String apiAnnotationGetFormDataParam(final Parameter element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(FormDataParam.class); - if (annotation.length == 0) { - return null; - } - return ((FormDataParam) annotation[0]).value(); - } - - public static Class[] apiAnnotationGetAsyncType(final Parameter element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class); - if (annotation.length == 0) { - return null; - } - return ((AsyncType) annotation[0]).value(); - } - - public static Class[] apiAnnotationGetAsyncType(final Method element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class); - if (annotation.length == 0) { - return null; - } - return ((AsyncType) annotation[0]).value(); - } - - public static List apiAnnotationGetConsumes(final Method element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(Consumes.class); - if (annotation.length == 0) { - return null; - } - return Arrays.asList(((Consumes) annotation[0]).value()); - } - - public static List apiAnnotationGetConsumes(final Class element) throws Exception { - final Annotation[] annotation = element.getDeclaredAnnotationsByType(Consumes.class); - if (annotation.length == 0) { - return null; - } - return Arrays.asList(((Consumes) annotation[0]).value()); - } - - public static List apiAnnotationGetConsumes(final Class clazz, final Method method) throws Exception { - final List data = apiAnnotationGetConsumes(method); - if (data != null) { - return data; - } - return apiAnnotationGetConsumes(clazz); - } - - public static boolean apiAnnotationIsContext(final Parameter element) throws Exception { - return element.getDeclaredAnnotationsByType(Context.class).length != 0; - } - - public static String convertInTypeScriptType(final List tmp, final boolean isList) { - String out = ""; - for (final ClassElement elem : tmp) { - if (out.length() != 0) { - out += " | "; - } - out += elem.tsTypeName; - if (isList) { - out += "[]"; - } - } - return out; - } - - public static String convertInTypeScriptCheckType(final List tmp) { - String out = ""; - for (final ClassElement elem : tmp) { - if (out.length() != 0) { - out += " | "; - } - out += elem.tsCheckType; - } - return out; - } - + record OrderedElement( String methodName, Method method) {} - + public static APIModel createSingleApi( final Class clazz, final Set> includeModel, @@ -303,14 +120,14 @@ public class DataFactoryTsApi { final GeneratedTypes previous) throws Exception { final StringBuilder builder = new StringBuilder(); // the basic path has no specific elements... - final String basicPath = apiAnnotationGetPath(clazz); + final String basicPath = ApiTool.apiAnnotationGetPath(clazz); final String classSimpleName = clazz.getSimpleName(); - + builder.append("export namespace "); builder.append(classSimpleName); builder.append(" {\n"); LOGGER.info("Parse Class for path: {} => {}", classSimpleName, basicPath); - + final List orderedElements = new ArrayList<>(); for (final Method method : clazz.getDeclaredMethods()) { final String methodName = method.getName(); @@ -318,26 +135,26 @@ public class DataFactoryTsApi { } final Comparator comparator = Comparator.comparing(OrderedElement::methodName); Collections.sort(orderedElements, comparator); - + for (final OrderedElement orderedElement : orderedElements) { final Method method = orderedElement.method(); final String methodName = orderedElement.methodName(); - final String methodPath = apiAnnotationGetPath(method); - final String methodType = apiAnnotationGetTypeRequest(method); + final String methodPath = ApiTool.apiAnnotationGetPath(method); + final String methodType = ApiTool.apiAnnotationGetTypeRequest(method); if (methodType == null) { LOGGER.error(" [{}] {} => {}/{} ==> No methode type @PATH, @GET ...", methodType, methodName, basicPath, methodPath); continue; } - final String methodDescription = apiAnnotationGetOperationDescription(method); - final List consumes = apiAnnotationGetConsumes(clazz, method); - List produces = apiAnnotationProduces(clazz, method); + final String methodDescription = ApiTool.apiAnnotationGetOperationDescription(method); + final List consumes = ApiTool.apiAnnotationGetConsumes(clazz, method); + List produces = ApiTool.apiAnnotationProduces(clazz, method); LOGGER.trace(" [{}] {} => {}/{}", methodType, methodName, basicPath, methodPath); if (methodDescription != null) { LOGGER.trace(" description: {}", methodDescription); } - final boolean needGenerateProgress = apiAnnotationTypeScriptProgress(method); - Class[] returnTypeModel = apiAnnotationGetAsyncType(method); + final boolean needGenerateProgress = ApiTool.apiAnnotationTypeScriptProgress(method); + Class[] returnTypeModel = ApiTool.apiAnnotationGetAsyncType(method); boolean isUnmanagedReturnType = false; boolean returnModelIsArray = false; List tmpReturn; @@ -357,8 +174,22 @@ public class DataFactoryTsApi { produces = null; } else if (returnTypeModelRaw == Map.class) { LOGGER.warn("Not manage the Map Model ... set any"); - returnTypeModel = new Class[] { Map.class }; - tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous); + final ParameterizedType listType = (ParameterizedType) method.getGenericReturnType(); + final Type typeKey = listType.getActualTypeArguments()[0]; + final Type typeValue = listType.getActualTypeArguments()[1]; + if (typeKey == String.class) { + if (typeValue instanceof ParameterizedType) { + final Type typeSubKey = listType.getActualTypeArguments()[0]; + final Type typeSubValue = listType.getActualTypeArguments()[1]; + if (typeKey == String.class) { + + } + } + } else { + LOGGER.warn("Not manage the Map Model ... set any"); + returnTypeModel = new Class[] { Map.class }; + tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous); + } } else if (returnTypeModelRaw == List.class) { final ParameterizedType listType = (ParameterizedType) method.getGenericReturnType(); returnTypeModelRaw = (Class) listType.getActualTypeArguments()[0]; @@ -399,12 +230,12 @@ public class DataFactoryTsApi { // LOGGER.info(" Parameters:"); for (final Parameter parameter : method.getParameters()) { // Security context are internal parameter (not available from API) - if (apiAnnotationIsContext(parameter)) { + if (ApiTool.apiAnnotationIsContext(parameter)) { continue; } final Class parameterType = parameter.getType(); String parameterTypeString; - final Class[] asyncType = apiAnnotationGetAsyncType(parameter); + final Class[] asyncType = ApiTool.apiAnnotationGetAsyncType(parameter); if (parameterType == List.class) { if (asyncType == null) { LOGGER.warn("Detect List param ==> not managed type ==> any[] !!!"); @@ -414,7 +245,7 @@ public class DataFactoryTsApi { for (final ClassElement elem : tmp) { includeModel.add(elem.model[0]); } - parameterTypeString = convertInTypeScriptType(tmp, true); + parameterTypeString = ApiTool.convertInTypeScriptType(tmp, true); } } else if (asyncType == null) { final ClassElement tmp = DataFactoryZod.createTable(parameterType, previous); @@ -425,11 +256,11 @@ public class DataFactoryTsApi { for (final ClassElement elem : tmp) { includeModel.add(elem.model[0]); } - parameterTypeString = convertInTypeScriptType(tmp, true); + parameterTypeString = ApiTool.convertInTypeScriptType(tmp, true); } - final String pathParam = apiAnnotationGetPathParam(parameter); - final String queryParam = apiAnnotationGetQueryParam(parameter); - final String formDataParam = apiAnnotationGetFormDataParam(parameter); + final String pathParam = ApiTool.apiAnnotationGetPathParam(parameter); + final String queryParam = ApiTool.apiAnnotationGetQueryParam(parameter); + final String formDataParam = ApiTool.apiAnnotationGetFormDataParam(parameter); if (queryParam != null) { queryParams.put(queryParam, parameterTypeString); } else if (pathParam != null) { @@ -480,7 +311,7 @@ public class DataFactoryTsApi { LOGGER.trace(" data type: {}", emptyElement.get(0)); } // ALL is good can generate the Elements - + if (methodDescription != null) { builder.append("\n\t/**\n\t * "); builder.append(methodDescription); @@ -552,7 +383,7 @@ public class DataFactoryTsApi { String isFist = null; for (final String elem : produces) { String lastElement = null; - + if (MediaType.APPLICATION_JSON.equals(elem)) { lastElement = "HTTPMimeType.JSON"; } @@ -584,7 +415,7 @@ public class DataFactoryTsApi { || tmpReturn.get(0).tsTypeName.equals("void")) { builder.append("void"); } else { - builder.append(convertInTypeScriptType(tmpReturn, returnModelIsArray)); + builder.append(ApiTool.convertInTypeScriptType(tmpReturn, returnModelIsArray)); } builder.append("> {"); if (tmpReturn.size() == 0 // @@ -656,7 +487,7 @@ public class DataFactoryTsApi { && !tmpReturn.get(0).tsTypeName.equals("void")) { builder.append(", "); // TODO: correct this it is really bad ... - builder.append(convertInTypeScriptCheckType(tmpReturn)); + builder.append(ApiTool.convertInTypeScriptCheckType(tmpReturn)); } builder.append(");"); builder.append("\n\t};"); @@ -664,7 +495,7 @@ public class DataFactoryTsApi { builder.append("\n}\n"); return new APIModel(builder.toString(), classSimpleName); } - + public static void generatePackage( final List> classApi, final List> classModel, @@ -676,7 +507,7 @@ public class DataFactoryTsApi { FileWriter myWriter = new FileWriter(pathPackage + File.separator + "model.ts"); myWriter.write(packageApi.toString()); myWriter.close(); - + final StringBuilder index = new StringBuilder(""" /** * Global import of the package @@ -704,5 +535,5 @@ public class DataFactoryTsApi { myWriter.close(); return; } - + } diff --git a/src/org/kar/archidata/externalRestApi/AnalyzeApi.java b/src/org/kar/archidata/externalRestApi/AnalyzeApi.java new file mode 100644 index 0000000..da3ce16 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/AnalyzeApi.java @@ -0,0 +1,5 @@ +package org.kar.archidata.externalRestApi; + +public class AnalyzeApi { + +} diff --git a/src/org/kar/archidata/externalRestApi/AnalyzeModel.java b/src/org/kar/archidata/externalRestApi/AnalyzeModel.java new file mode 100644 index 0000000..3918435 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/AnalyzeModel.java @@ -0,0 +1,5 @@ +package org.kar.archidata.externalRestApi; + +public class AnalyzeModel { + +} diff --git a/src/org/kar/archidata/externalRestApi/GeneratePythonApi.java b/src/org/kar/archidata/externalRestApi/GeneratePythonApi.java new file mode 100644 index 0000000..d48e6a5 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/GeneratePythonApi.java @@ -0,0 +1,5 @@ +package org.kar.archidata.externalRestApi; + +public class GeneratePythonApi { + +} diff --git a/src/org/kar/archidata/externalRestApi/GeneratePythonModel.java b/src/org/kar/archidata/externalRestApi/GeneratePythonModel.java new file mode 100644 index 0000000..5a96ed4 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/GeneratePythonModel.java @@ -0,0 +1,5 @@ +package org.kar.archidata.externalRestApi; + +public class GeneratePythonModel { + +} diff --git a/src/org/kar/archidata/externalRestApi/GenerateTsApi.java b/src/org/kar/archidata/externalRestApi/GenerateTsApi.java new file mode 100644 index 0000000..de979c2 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/GenerateTsApi.java @@ -0,0 +1,5 @@ +package org.kar.archidata.externalRestApi; + +public class GenerateTsApi { + +} diff --git a/src/org/kar/archidata/externalRestApi/GenerateTsModel.java b/src/org/kar/archidata/externalRestApi/GenerateTsModel.java new file mode 100644 index 0000000..ab6d35f --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/GenerateTsModel.java @@ -0,0 +1,5 @@ +package org.kar.archidata.externalRestApi; + +public class GenerateTsModel { + +} diff --git a/src/org/kar/archidata/externalRestApi/model/ApiGroupModel.java b/src/org/kar/archidata/externalRestApi/model/ApiGroupModel.java new file mode 100644 index 0000000..0e02354 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/model/ApiGroupModel.java @@ -0,0 +1,60 @@ +package org.kar.archidata.externalRestApi.model; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// Temporary class that permit to order the list of API. +record OrderedElement( + String methodName, + Method method) {} + +public class ApiGroupModel { + static final Logger LOGGER = LoggerFactory.getLogger(ApiGroupModel.class); + + // Name of the REST end-point name + public String restEndPoint; + // Name of the Class + String name; + // Origin class reference + Class originClass; + // List of all API + public List interfaces; + + public ApiGroupModel(final Class clazz, final ModelGroup previousModel) throws Exception { + this.originClass = clazz; + // the basic path has no specific elements... + this.restEndPoint = ApiTool.apiAnnotationGetPath(clazz); + this.name = clazz.getSimpleName(); + + final List consumes = ApiTool.apiAnnotationGetConsumes(clazz); + final List produces = ApiTool.apiAnnotationProduces(clazz); + + // Get all method to order it. This permit to stabilize the generation. + // JAVA has dynamic allocation of member order, then we need to order it?. + final List orderedElements = new ArrayList<>(); + for (final Method method : clazz.getDeclaredMethods()) { + final String methodName = method.getName(); + orderedElements.add(new OrderedElement(methodName, method)); + } + final Comparator comparator = Comparator.comparing(OrderedElement::methodName); + Collections.sort(orderedElements, comparator); + + for (final OrderedElement orderedElement : orderedElements) { + // Check if the path is available + final RestTypeRequest methodType = ApiTool.apiAnnotationGetTypeRequest2(orderedElement.method()); + if (methodType == null) { + LOGGER.info(" [{}] {} ==> No methode type @PATH, @GET ...", methodType, orderedElement.methodName()); + continue; + } + final ApiModel model = new ApiModel(clazz, orderedElement.method(), this.restEndPoint, consumes, produces, + previousModel); + this.interfaces.add(model); + } + } +} diff --git a/src/org/kar/archidata/externalRestApi/model/ApiModel.java b/src/org/kar/archidata/externalRestApi/model/ApiModel.java new file mode 100644 index 0000000..1099851 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/model/ApiModel.java @@ -0,0 +1,239 @@ +package org.kar.archidata.externalRestApi.model; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.kar.archidata.dataAccess.DataFactoryZod; +import org.kar.archidata.dataAccess.DataFactoryZod.ClassElement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.ws.rs.core.Response; + +public class ApiModel { + static final Logger LOGGER = LoggerFactory.getLogger(ApiModel.class); + + Class originClass; + Method orignMethod; + + // Name of the REST end-point name + public String restEndPoint; + // Type of the request: + public RestTypeRequest restTypeRequest; + // Description of the API + public String description; + // need to generate the progression of stream (if possible) + boolean needGenerateProgress; + + // List of types returned by the API + public List returnTypes = new ArrayList<>();; + // Name of the API (function name) + public String name; + // list of all parameters (/{key}/... + public Map parameters = new HashMap<>(); + // list of all query (?key...) + public Map queries = new HashMap<>(); + + // Possible input type of the REST API + public List consumes = new ArrayList<>(); + // Possible output type of the REST API + public List produces = new ArrayList<>(); + + private void updateReturnTypes(final Method method, final ModelGroup previousModel) { + // get return type from the user specification: + final Class[] returnTypeModel = ApiTool.apiAnnotationGetAsyncType(method); + + if (returnTypeModel != null) { + if (returnTypeModel.length == 0) { + throw new IOException("Create a @AsyncType with empty elements ..."); + } + for (final Class clazz : returnTypeModel) { + if (clazz == Void.class || clazz == void.class) { + this.returnTypes.add(previousModel.add(Void.class)); + } else if (clazz == List.class) { + throw new IOException("Unmanaged List.class in @AsyncType."); + } else if (clazz == Map.class) { + throw new IOException("Unmanaged Map.class in @AsyncType."); + } else { + this.returnTypes.add(previousModel.add(clazz)); + } + } + return; + } + + final Class returnTypeModelRaw = method.getReturnType(); + LOGGER.info("Get type: {}", returnTypeModelRaw); + if (returnTypeModelRaw == Map.class) { + LOGGER.warn("Not manage the Map Model ... set any"); + final ParameterizedType listType = (ParameterizedType) method.getGenericReturnType(); + this.returnTypes.add(new ClassMapModel(listType, previousModel)); + return; + } + if (returnTypeModelRaw == List.class) { + final ParameterizedType listType = (ParameterizedType) method.getGenericReturnType(); + this.returnTypes.add(new ClassListModel(listType, previousModel)); + return; + } + this.returnTypes.add(previousModel.add(returnTypeModelRaw)); + } + + public ApiModel(final Class clazz, final Method method, final String baseRestEndPoint, + final List consume, final List produce, final ModelGroup previousModel) throws Exception { + this.originClass = clazz; + this.orignMethod = method; + + final String methodPath = ApiTool.apiAnnotationGetPath(method); + final String methodType = ApiTool.apiAnnotationGetTypeRequest(method); + final String methodName = method.getName(); + + this.description = ApiTool.apiAnnotationGetOperationDescription(method); + this.consumes = ApiTool.apiAnnotationGetConsumes2(consume, method); + this.produces = ApiTool.apiAnnotationProduces2(produce, method); + LOGGER.trace(" [{}] {} => {}/{}", methodType, methodName, baseRestEndPoint, methodPath); + this.needGenerateProgress = ApiTool.apiAnnotationTypeScriptProgress(method); + + Class[] returnTypeModel = ApiTool.apiAnnotationGetAsyncType(method); + boolean isUnmanagedReturnType = false; + boolean returnModelIsArray = false; + List tmpReturn; + if (returnTypeModel == null) { + Class returnTypeModelRaw = method.getReturnType(); + LOGGER.info("Get type: {}", returnTypeModelRaw); + if (returnTypeModelRaw == Response.class) { + LOGGER.info("Get type: {}", returnTypeModelRaw); + } + if (returnTypeModelRaw == Response.class || returnTypeModelRaw == void.class + || returnTypeModelRaw == Void.class) { + if (returnTypeModelRaw == Response.class) { + isUnmanagedReturnType = true; + } + returnTypeModel = new Class[] { Void.class }; + tmpReturn = new ArrayList<>(); + this.produces = null; + } else if (returnTypeModelRaw == Map.class) { + LOGGER.warn("Not manage the Map Model ... set any"); + final ParameterizedType listType = (ParameterizedType) method.getGenericReturnType(); + final Type typeKey = listType.getActualTypeArguments()[0]; + final Type typeValue = listType.getActualTypeArguments()[1]; + if (typeKey == String.class) { + if (typeValue instanceof ParameterizedType) { + final Type typeSubKey = listType.getActualTypeArguments()[0]; + final Type typeSubValue = listType.getActualTypeArguments()[1]; + if (typeKey == String.class) { + + } + } + } else { + LOGGER.warn("Not manage the Map Model ... set any"); + returnTypeModel = new Class[] { Map.class }; + tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous); + } + } else if (returnTypeModelRaw == List.class) { + final ParameterizedType listType = (ParameterizedType) method.getGenericReturnType(); + returnTypeModelRaw = (Class) listType.getActualTypeArguments()[0]; + returnModelIsArray = true; + returnTypeModel = new Class[] { returnTypeModelRaw }; + tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous); + } else { + returnTypeModel = new Class[] { returnTypeModelRaw }; + tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous); + } + } else if (returnTypeModel.length >= 0 && (returnTypeModel[0] == Response.class + || returnTypeModel[0] == Void.class || returnTypeModel[0] == void.class)) { + if (returnTypeModel[0] == Response.class) { + isUnmanagedReturnType = true; + } + returnTypeModel = new Class[] { Void.class }; + tmpReturn = new ArrayList<>(); + this.produces = null; + } else if (returnTypeModel.length > 0 && returnTypeModel[0] == Map.class) { + LOGGER.warn("Not manage the Map Model ..."); + returnTypeModel = new Class[] { Map.class }; + tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous); + } else { + tmpReturn = DataFactoryZod.createTables(returnTypeModel, previous); + } + for (final ClassElement elem : tmpReturn) { + includeModel.add(elem.model[0]); + includeCheckerModel.add(elem.model[0]); + } + LOGGER.trace(" return: {}", tmpReturn.size()); + for (final ClassElement elem : tmpReturn) { + LOGGER.trace(" - {}", elem.tsTypeName); + } + + final Map queryParams = new HashMap<>(); + final Map pathParams = new HashMap<>(); + final Map formDataParams = new HashMap<>(); + final List emptyElement = new ArrayList<>(); + // LOGGER.info(" Parameters:"); + for (final Parameter parameter : method.getParameters()) { + // Security context are internal parameter (not available from API) + if (ApiTool.apiAnnotationIsContext(parameter)) { + continue; + } + final Class parameterType = parameter.getType(); + String parameterTypeString; + final Class[] asyncType = ApiTool.apiAnnotationGetAsyncType(parameter); + if (parameterType == List.class) { + if (asyncType == null) { + LOGGER.warn("Detect List param ==> not managed type ==> any[] !!!"); + parameterTypeString = "any[]"; + } else { + final List tmp = DataFactoryZod.createTables(asyncType, previous); + for (final ClassElement elem : tmp) { + includeModel.add(elem.model[0]); + } + parameterTypeString = ApiTool.convertInTypeScriptType(tmp, true); + } + } else if (asyncType == null) { + final ClassElement tmp = DataFactoryZod.createTable(parameterType, previous); + includeModel.add(tmp.model[0]); + parameterTypeString = tmp.tsTypeName; + } else { + final List tmp = DataFactoryZod.createTables(asyncType, previous); + for (final ClassElement elem : tmp) { + includeModel.add(elem.model[0]); + } + parameterTypeString = ApiTool.convertInTypeScriptType(tmp, true); + } + final String pathParam = ApiTool.apiAnnotationGetPathParam(parameter); + final String queryParam = ApiTool.apiAnnotationGetQueryParam(parameter); + final String formDataParam = ApiTool.apiAnnotationGetFormDataParam(parameter); + if (queryParam != null) { + queryParams.put(queryParam, parameterTypeString); + } else if (pathParam != null) { + pathParams.put(pathParam, parameterTypeString); + } else if (formDataParam != null) { + formDataParams.put(formDataParam, parameterTypeString); + } else if (asyncType != null) { + final List tmp = DataFactoryZod.createTables(asyncType, previous); + parameterTypeString = ""; + for (final ClassElement elem : tmp) { + includeModel.add(elem.model[0]); + if (parameterTypeString.length() != 0) { + parameterTypeString += " | "; + } + parameterTypeString += elem.tsTypeName; + } + emptyElement.add(parameterTypeString); + } else if (parameterType == List.class) { + parameterTypeString = "any[]"; + final Class plop = parameterType.arrayType(); + LOGGER.info("ArrayType = {}", plop); + emptyElement.add(parameterTypeString); + } else { + final ClassElement tmp = DataFactoryZod.createTable(parameterType, previous); + includeModel.add(tmp.model[0]); + emptyElement.add(tmp.tsTypeName); + } + } + } +} diff --git a/src/org/kar/archidata/externalRestApi/model/ApiTool.java b/src/org/kar/archidata/externalRestApi/model/ApiTool.java new file mode 100644 index 0000000..ed91546 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/model/ApiTool.java @@ -0,0 +1,233 @@ +package org.kar.archidata.externalRestApi.model; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.List; + +import org.glassfish.jersey.media.multipart.FormDataParam; +import org.kar.archidata.annotation.AsyncType; +import org.kar.archidata.annotation.TypeScriptProgress; +import org.kar.archidata.dataAccess.DataFactoryZod.ClassElement; + +import io.swagger.v3.oas.annotations.Operation; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PATCH; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + +public class ApiTool { + + public static String apiAnnotationGetPath(final Class element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Path.class); + if (annotation.length == 0) { + return null; + } + return ((Path) annotation[0]).value(); + } + + public static List apiAnnotationProduces(final Class element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Produces.class); + if (annotation.length == 0) { + return null; + } + return Arrays.asList(((Produces) annotation[0]).value()); + } + + public static List apiAnnotationProduces(final Method element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Produces.class); + if (annotation.length == 0) { + return null; + } + return Arrays.asList(((Produces) annotation[0]).value()); + } + + public static boolean apiAnnotationTypeScriptProgress(final Method element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(TypeScriptProgress.class); + if (annotation.length == 0) { + return false; + } + return true; + } + + public static List apiAnnotationProduces(final Class clazz, final Method method) throws Exception { + final List data = apiAnnotationProduces(method); + if (data != null) { + return data; + } + return apiAnnotationProduces(clazz); + } + + public static List apiAnnotationProduces2(final List parentProduce, final Method method) + throws Exception { + final List data = apiAnnotationProduces(method); + if (data != null) { + return data; + } + return parentProduce; + } + + public static String apiAnnotationGetOperationDescription(final Method element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Operation.class); + if (annotation.length == 0) { + return null; + } + return ((Operation) annotation[0]).description(); + } + + public static String apiAnnotationGetPath(final Method element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Path.class); + if (annotation.length == 0) { + return null; + } + return ((Path) annotation[0]).value(); + } + + public static String apiAnnotationGetTypeRequest(final Method element) throws Exception { + if (element.getDeclaredAnnotationsByType(GET.class).length == 1) { + return "GET"; + } + if (element.getDeclaredAnnotationsByType(POST.class).length == 1) { + return "POST"; + } + if (element.getDeclaredAnnotationsByType(PUT.class).length == 1) { + return "PUT"; + } + if (element.getDeclaredAnnotationsByType(PATCH.class).length == 1) { + return "PATCH"; + } + if (element.getDeclaredAnnotationsByType(DELETE.class).length == 1) { + return "DELETE"; + } + return null; + } + + public static RestTypeRequest apiAnnotationGetTypeRequest2(final Method element) throws Exception { + if (element.getDeclaredAnnotationsByType(GET.class).length == 1) { + return RestTypeRequest.GET; + } + if (element.getDeclaredAnnotationsByType(POST.class).length == 1) { + return RestTypeRequest.POST; + } + if (element.getDeclaredAnnotationsByType(PUT.class).length == 1) { + return RestTypeRequest.PUT; + } + if (element.getDeclaredAnnotationsByType(PATCH.class).length == 1) { + return RestTypeRequest.PATCH; + } + if (element.getDeclaredAnnotationsByType(DELETE.class).length == 1) { + return RestTypeRequest.DELETE; + } + return null; + } + + public static String apiAnnotationGetPathParam(final Parameter element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(PathParam.class); + if (annotation.length == 0) { + return null; + } + return ((PathParam) annotation[0]).value(); + } + + public static String apiAnnotationGetQueryParam(final Parameter element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(QueryParam.class); + if (annotation.length == 0) { + return null; + } + return ((QueryParam) annotation[0]).value(); + } + + public static String apiAnnotationGetFormDataParam(final Parameter element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(FormDataParam.class); + if (annotation.length == 0) { + return null; + } + return ((FormDataParam) annotation[0]).value(); + } + + public static Class[] apiAnnotationGetAsyncType(final Parameter element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class); + if (annotation.length == 0) { + return null; + } + return ((AsyncType) annotation[0]).value(); + } + + public static Class[] apiAnnotationGetAsyncType(final Method element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(AsyncType.class); + if (annotation.length == 0) { + return null; + } + return ((AsyncType) annotation[0]).value(); + } + + public static List apiAnnotationGetConsumes(final Method element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Consumes.class); + if (annotation.length == 0) { + return null; + } + return Arrays.asList(((Consumes) annotation[0]).value()); + } + + public static List apiAnnotationGetConsumes(final Class element) throws Exception { + final Annotation[] annotation = element.getDeclaredAnnotationsByType(Consumes.class); + if (annotation.length == 0) { + return null; + } + return Arrays.asList(((Consumes) annotation[0]).value()); + } + + public static List apiAnnotationGetConsumes(final Class clazz, final Method method) throws Exception { + final List data = apiAnnotationGetConsumes(method); + if (data != null) { + return data; + } + return apiAnnotationGetConsumes(clazz); + } + + public static List apiAnnotationGetConsumes2(final List parentConsume, final Method method) + throws Exception { + final List data = apiAnnotationGetConsumes(method); + if (data != null) { + return data; + } + return parentConsume; + } + + public static boolean apiAnnotationIsContext(final Parameter element) throws Exception { + return element.getDeclaredAnnotationsByType(Context.class).length != 0; + } + + public static String convertInTypeScriptType(final List tmp, final boolean isList) { + String out = ""; + for (final ClassElement elem : tmp) { + if (out.length() != 0) { + out += " | "; + } + out += elem.tsTypeName; + if (isList) { + out += "[]"; + } + } + return out; + } + + public static String convertInTypeScriptCheckType(final List tmp) { + String out = ""; + for (final ClassElement elem : tmp) { + if (out.length() != 0) { + out += " | "; + } + out += elem.tsCheckType; + } + return out; + } +} diff --git a/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java b/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java new file mode 100644 index 0000000..641d8a8 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/model/ClassEnumModel.java @@ -0,0 +1,8 @@ +package org.kar.archidata.externalRestApi.model; + +public class ClassEnumModel extends ClassModel { + + protected ClassEnumModel(final Class clazz) { + this.originClasses.add(clazz); + } +} diff --git a/src/org/kar/archidata/externalRestApi/model/ClassListModel.java b/src/org/kar/archidata/externalRestApi/model/ClassListModel.java new file mode 100644 index 0000000..94529e1 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/model/ClassListModel.java @@ -0,0 +1,19 @@ +package org.kar.archidata.externalRestApi.model; + +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public class ClassListModel extends ClassModel { + public ClassModel valueModel; + + public ClassListModel(final ClassModel valueModel) { + this.valueModel = valueModel; + } + + public ClassListModel(final ParameterizedType listType, final ModelGroup previousModel) throws IOException { + final Type model = listType.getActualTypeArguments()[0]; + this.valueModel = getModel(model, previousModel); + } + +} diff --git a/src/org/kar/archidata/externalRestApi/model/ClassMapModel.java b/src/org/kar/archidata/externalRestApi/model/ClassMapModel.java new file mode 100644 index 0000000..4edb14f --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/model/ClassMapModel.java @@ -0,0 +1,20 @@ +package org.kar.archidata.externalRestApi.model; + +import java.io.IOException; +import java.lang.reflect.ParameterizedType; + +public class ClassMapModel extends ClassModel { + public ClassModel keyModel; + public ClassModel valueModel; + + public ClassMapModel(final ClassModel keyModel, final ClassModel valueModel) { + this.keyModel = keyModel; + this.valueModel = valueModel; + } + + public ClassMapModel(final ParameterizedType listType, final ModelGroup previousModel) throws IOException { + this.keyModel = getModel(listType.getActualTypeArguments()[0], previousModel); + this.valueModel = getModel(listType.getActualTypeArguments()[1], previousModel); + } + +} diff --git a/src/org/kar/archidata/externalRestApi/model/ClassModel.java b/src/org/kar/archidata/externalRestApi/model/ClassModel.java new file mode 100644 index 0000000..6cfce90 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/model/ClassModel.java @@ -0,0 +1,35 @@ +package org.kar.archidata.externalRestApi.model; + +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public abstract class ClassModel { + public List> originClasses = new ArrayList<>(); + + protected boolean isCompatible(final Class clazz) { + return this.originClasses.contains(clazz); + } + + public ClassModel getModel(final Type type, final ModelGroup previousModel) throws IOException { + if (type == List.class) { + if (type instanceof final ParameterizedType parameterizedType) { + return new ClassListModel(parameterizedType, previousModel); + } else { + throw new IOException("Fail to manage parametrized type..."); + } + } + if (type == Map.class) { + if (type instanceof final ParameterizedType parameterizedType) { + return new ClassMapModel(parameterizedType, previousModel); + } else { + throw new IOException("Fail to manage parametrized type..."); + } + } + return previousModel.add((Class) type); + } + +} diff --git a/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java b/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java new file mode 100644 index 0000000..58ff668 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/model/ClassObjectModel.java @@ -0,0 +1,8 @@ +package org.kar.archidata.externalRestApi.model; + +public class ClassObjectModel extends ClassModel { + + public ClassObjectModel(final Class clazz) { + this.originClasses.add(clazz); + } +} diff --git a/src/org/kar/archidata/externalRestApi/model/ModelGroup.java b/src/org/kar/archidata/externalRestApi/model/ModelGroup.java new file mode 100644 index 0000000..c467a79 --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/model/ModelGroup.java @@ -0,0 +1,22 @@ +package org.kar.archidata.externalRestApi.model; + +import java.util.ArrayList; +import java.util.List; + +public class ModelGroup { + public final List previousModel = new ArrayList<>(); + + public ClassModel add(final Class clazz) { + for (final ClassModel value : this.previousModel) { + if (value.isCompatible(clazz)) { + return value; + } + } + if (clazz.isEnum()) { + final ClassModel elem = new ClassEnumModel(clazz); + } + // create new model: + + return null; + } +} diff --git a/src/org/kar/archidata/externalRestApi/model/RestTypeRequest.java b/src/org/kar/archidata/externalRestApi/model/RestTypeRequest.java new file mode 100644 index 0000000..6d76c7a --- /dev/null +++ b/src/org/kar/archidata/externalRestApi/model/RestTypeRequest.java @@ -0,0 +1,5 @@ +package org.kar.archidata.externalRestApi.model; + +public enum RestTypeRequest { + GET, POST, PUT, PATCH, DELETE +}