From c3a2e63e37ca08fb773c802b5326ac344aeade6a Mon Sep 17 00:00:00 2001 From: Tomasz Wozniak Date: Fri, 13 Dec 2019 00:37:57 +0100 Subject: [PATCH] spring-rest-2ts: jax-rs implementation --- pom.xml | 1 + spring-rest2ts-jax-rs/pom.xml | 29 ++ .../converters/JaxRsRestToTsConverter.java | 138 ++++++++ ...ingAnnotationsBasedRestClassConverter.java | 299 ++++++++++++++++++ .../converters/SpringRestToTsConverter.java | 285 +---------------- .../spring/MethodParameterEntity.java | 31 ++ .../spring/PathVariableEntity.java | 14 + .../spring/RequestBodyEntity.java | 23 ++ .../spring/RequestParamEntity.java | 24 ++ 9 files changed, 575 insertions(+), 269 deletions(-) create mode 100644 spring-rest2ts-jax-rs/pom.xml create mode 100644 spring-rest2ts-jax-rs/src/main/java/com/blueveery/springrest2ts/converters/JaxRsRestToTsConverter.java create mode 100644 spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/converters/SpringAnnotationsBasedRestClassConverter.java create mode 100644 spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/MethodParameterEntity.java create mode 100644 spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/PathVariableEntity.java create mode 100644 spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/RequestBodyEntity.java create mode 100644 spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/RequestParamEntity.java diff --git a/pom.xml b/pom.xml index 2b99875..153001e 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,7 @@ spring-rest2ts-generator spring-rest2ts-jackson spring-rest2ts-spring + spring-rest2ts-jax-rs spring-rest2ts-spring-data diff --git a/spring-rest2ts-jax-rs/pom.xml b/spring-rest2ts-jax-rs/pom.xml new file mode 100644 index 0000000..e6d6091 --- /dev/null +++ b/spring-rest2ts-jax-rs/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + com.blue-veery + spring-rest2ts + 1.2.2 + + + spring-rest2ts-jax-rs + 1.2.2 + + + + com.blue-veery + spring-rest2ts-spring + 1.2.2 + + + javax + javaee-api + 8.0.1 + provided + + + + \ No newline at end of file diff --git a/spring-rest2ts-jax-rs/src/main/java/com/blueveery/springrest2ts/converters/JaxRsRestToTsConverter.java b/spring-rest2ts-jax-rs/src/main/java/com/blueveery/springrest2ts/converters/JaxRsRestToTsConverter.java new file mode 100644 index 0000000..69510c9 --- /dev/null +++ b/spring-rest2ts-jax-rs/src/main/java/com/blueveery/springrest2ts/converters/JaxRsRestToTsConverter.java @@ -0,0 +1,138 @@ +package com.blueveery.springrest2ts.converters; + +import com.blueveery.springrest2ts.implgens.ImplementationGenerator; +import com.blueveery.springrest2ts.naming.ClassNameMapper; +import com.blueveery.springrest2ts.spring.PathVariableEntity; +import com.blueveery.springrest2ts.spring.RequestBodyEntity; +import com.blueveery.springrest2ts.spring.RequestMappingEntity; +import com.blueveery.springrest2ts.spring.RequestParamEntity; +import com.blueveery.springrest2ts.tsmodel.TSClass; +import com.blueveery.springrest2ts.tsmodel.TSMethod; +import com.blueveery.springrest2ts.tsmodel.TSParameter; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.ws.rs.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class JaxRsRestToTsConverter extends SpringAnnotationsBasedRestClassConverter { + protected JaxRsRestToTsConverter(ImplementationGenerator implementationGenerator) { + super(implementationGenerator); + } + + public JaxRsRestToTsConverter(ImplementationGenerator implementationGenerator, ClassNameMapper classNameMapper) { + super(implementationGenerator, classNameMapper); + } + + @Override + protected void addClassAnnotations(Class javaClass, TSClass tsClass) { + RequestMappingEntity requestMapping = null; + Annotation[] annotationsByType = javaClass.getAnnotationsByType(Path.class); + if (annotationsByType != null) { + Path path = (Path) annotationsByType[0]; + requestMapping = new RequestMappingEntity(); + requestMapping.setPath(new String[]{path.value()}); + tsClass.addAllAnnotations(new Annotation[]{requestMapping}); + } + + } + + @Override + protected void addMethodAnnotations(Method method, TSMethod tsMethod) { + RequestMappingEntity requestMapping = new RequestMappingEntity(); + Set requestMethodSet = new HashSet<>(); + for (Annotation annotation : method.getAnnotations()) { + if(annotation instanceof Path){ + Path path = (Path) annotation; + requestMapping.setPath(new String[]{path.value()}); + } + if(annotation instanceof GET){ + requestMethodSet.add(RequestMethod.GET); + } + if(annotation instanceof POST){ + requestMethodSet.add(RequestMethod.POST); + } + if(annotation instanceof PUT){ + requestMethodSet.add(RequestMethod.PUT); + } + if(annotation instanceof DELETE){ + requestMethodSet.add(RequestMethod.DELETE); + } + if(annotation instanceof Produces){ + Produces produces = (Produces) annotation; + requestMapping.setProduces(produces.value()); + } + if(annotation instanceof Consumes){ + Consumes consumes = (Consumes) annotation; + requestMapping.setConsumes(consumes.value()); + } + } + if (!requestMethodSet.isEmpty()) { + requestMapping.setMethod(requestMethodSet.toArray(new RequestMethod[0])); + tsMethod.addAllAnnotations(new Annotation[]{requestMapping}); + } + + } + + @Override + protected void addParameterAnnotations(Parameter parameter, TSParameter tsParameter) { + RequestParamEntity requestParam = null; + PathVariableEntity pathVariable = null; + DefaultValue defaultValue = null; + for (Annotation annotation : parameter.getAnnotations()) { + if(annotation instanceof PathParam){ + PathParam pathParam = (PathParam) annotation; + pathVariable = new PathVariableEntity(); + pathVariable.setName(pathParam.value()); + } + if(annotation instanceof QueryParam){ + QueryParam queryParam = (QueryParam) annotation; + requestParam = new RequestParamEntity(); + requestParam.setName(queryParam.value()); + } + if(annotation instanceof DefaultValue){ + defaultValue = (DefaultValue) annotation; + } + } + + if (requestParam != null) { + requestParam.setRequired(defaultValue == null); + tsParameter.addAllAnnotations(new Annotation[]{requestParam}); + } + if (pathVariable != null) { + pathVariable.setRequired(defaultValue == null); + tsParameter.addAllAnnotations(new Annotation[]{pathVariable}); + } + if (tsParameter.getAnnotationList().isEmpty()) { + boolean isPutOrPost = false; + List annotationList = tsParameter.getTsMethod().getAnnotationList(); + RequestMapping requestMapping = (RequestMapping) annotationList.stream().filter(a -> a instanceof RequestMapping).findFirst().orElse(null); + if (requestMapping == null) { + List classAnnotationList = tsParameter.getTsMethod().getOwner().getAnnotationList(); + requestMapping = (RequestMapping) classAnnotationList.stream().filter(a -> a instanceof RequestMapping).findFirst().orElse(null); + } + for (RequestMethod requestMethod : requestMapping.method()) { + switch (requestMethod){ + case POST: + case PUT: isPutOrPost = true; break; + } + } + if (isPutOrPost) { + RequestBodyEntity requestBodyEntity = new RequestBodyEntity(); + requestBodyEntity.setRequired(true); + tsParameter.getTsMethod().getAnnotationList().add(requestBodyEntity); + } + } + } + + @Override + protected Type handleImplementationSpecificReturnTypes(Method method) { + return method.getGenericReturnType(); + } +} diff --git a/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/converters/SpringAnnotationsBasedRestClassConverter.java b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/converters/SpringAnnotationsBasedRestClassConverter.java new file mode 100644 index 0000000..dac458a --- /dev/null +++ b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/converters/SpringAnnotationsBasedRestClassConverter.java @@ -0,0 +1,299 @@ +package com.blueveery.springrest2ts.converters; + +import com.blueveery.springrest2ts.extensions.RestConversionExtension; +import com.blueveery.springrest2ts.implgens.ImplementationGenerator; +import com.blueveery.springrest2ts.naming.ClassNameMapper; +import com.blueveery.springrest2ts.spring.RequestMappingUtility; +import com.blueveery.springrest2ts.tsmodel.*; +import com.blueveery.springrest2ts.tsmodel.generics.TSClassReference; +import com.blueveery.springrest2ts.tsmodel.generics.TSInterfaceReference; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.util.*; +import java.util.stream.Collectors; + +public abstract class SpringAnnotationsBasedRestClassConverter extends RestClassConverter { + protected SpringAnnotationsBasedRestClassConverter(ImplementationGenerator implementationGenerator) { + super(implementationGenerator); + } + + public SpringAnnotationsBasedRestClassConverter(ImplementationGenerator implementationGenerator, ClassNameMapper classNameMapper) { + super(implementationGenerator, classNameMapper); + } + + @Override + public boolean preConverted(JavaPackageToTsModuleConverter javaPackageToTsModuleConverter, Class javaClass){ + if(TypeMapper.map(javaClass) == TypeMapper.tsAny && !javaClass.isInterface()){ + TSModule tsModule = javaPackageToTsModuleConverter.getTsModule(javaClass); + String tsClassName = createTsClassName(javaClass); + TSClass tsClass = new TSClass(tsClassName, tsModule, implementationGenerator); + tsModule.addScopedType(tsClass); + TypeMapper.registerTsType(javaClass, tsClass); + return true; + } + return false; + } + + @Override + public void convert(Class javaClass, NullableTypesStrategy nullableTypesStrategy) { + TSClassReference tsClassReference = (TSClassReference) TypeMapper.map(javaClass); + TSClass tsClass = tsClassReference.getReferencedType(); + + convertFormalTypeParameters(javaClass.getTypeParameters(), tsClassReference); + setSupperClass(javaClass, tsClass); + addClassAnnotations(javaClass, tsClass); + + TSMethod tsConstructorMethod = new TSMethod("constructor", tsClass, null, implementationGenerator, false, true); + tsClass.addTsMethod(tsConstructorMethod); + List restMethodList = filterRestMethods(javaClass); + Map methodNamesMap = new HashMap<>(); + + for (Method method: restMethodList) { + + Map variableNameToJavaType = new HashMap<>(); + Class declaringClass = method.getDeclaringClass(); + if(declaringClass != javaClass && method.getDeclaringClass().isInterface()){ + variableNameToJavaType = fillVariableNameToJavaType(javaClass, declaringClass); + } + + Type genericReturnType = handleImplementationSpecificReturnTypes(method); + String methodName = mapMethodName(restMethodList, methodNamesMap, method); + TSType methodReturnType = TypeMapper.map(resolveTypeVariable(genericReturnType, variableNameToJavaType)); + tsClass.getModule().scopedTypeUsage(methodReturnType); + TSMethod tsMethod = new TSMethod(methodName, tsClass, methodReturnType, implementationGenerator, false, false); + for (Parameter parameter:method.getParameters()) { + Type parameterType = resolveTypeVariable(parameter.getParameterizedType(), variableNameToJavaType); + TSParameter tsParameter = new TSParameter(parameter.getName(), TypeMapper.map(parameterType), tsMethod, implementationGenerator); + addParameterAnnotations(parameter, tsParameter); + if (parameterIsMapped(tsParameter)) { + tsClass.getModule().scopedTypeUsage(tsParameter.getType()); + setOptional(tsParameter); + nullableTypesStrategy.setAsNullableType(parameter.getParameterizedType(), parameter.getDeclaredAnnotations(), tsParameter); + tsMethod.getParameterList().add(tsParameter); + } + conversionListener.tsParameterCreated(parameter, tsParameter); + } + addMethodAnnotations(method, tsMethod); + tsClass.addTsMethod(tsMethod); + conversionListener.tsMethodCreated(method, tsMethod); + } + + implementationGenerator.addComplexTypeUsage(tsClass); + conversionListener.tsScopedTypeCreated(javaClass, tsClass); + } + + protected abstract void addClassAnnotations(Class javaClass, TSClass tsClass); + + protected abstract void addMethodAnnotations(Method method, TSMethod tsMethod); + + protected abstract void addParameterAnnotations(Parameter parameter, TSParameter tsParameter); + + protected abstract Type handleImplementationSpecificReturnTypes(Method method); + + private Type resolveTypeVariable(Type type, Map variableNameToJavaType) { + if (type instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) type; + Type resolvedType = variableNameToJavaType.get(typeVariable.getName()); + if (resolvedType != null) { + return resolvedType; + } + } + return type; + } + + private Map fillVariableNameToJavaType(Class javaClass, Class declaringClass) { + Map typeParametersMap = new HashMap<>(); + for (Type type:javaClass.getGenericInterfaces()){ + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + if (parameterizedType.getRawType() == declaringClass) { + for (int i = 0; i < parameterizedType.getActualTypeArguments().length; i++) { + TypeVariable typeParameter = declaringClass.getTypeParameters()[i]; + Type actualTypeArgument = parameterizedType.getActualTypeArguments()[i]; + typeParametersMap.put(typeParameter.getName(), actualTypeArgument); + } + return typeParametersMap; + } + return fillVariableNameToJavaType((Class) type, declaringClass); + } + } + return typeParametersMap; + } + + private List filterRestMethods(Class javaClass) { + List restMethodList = new ArrayList<>(); + for (Method method : javaClass.getMethods()) { + if(method.getDeclaringClass() == javaClass || method.getDeclaringClass().isInterface()) { + for (Method nextMethod : javaClass.getSuperclass().getMethods()) { + if(nextMethod.equals(method)){ + continue; // parent class contains method from interface + } + } + if (isRestMethod(method)) { + restMethodList.add(method); + } + } + } + return restMethodList; + } + + private String mapMethodName(List restMethodList, Map methodNamesMap, Method currentMethod) { + StringBuilder methodName = methodNamesMap.get(currentMethod); + if (methodName != null) { + return methodName.toString(); + } + List overloadedMethods = restMethodList.stream().filter(m -> m.getName().equals(currentMethod.getName())).collect(Collectors.toList()); + if (overloadedMethods.size() == 1) { + return currentMethod.getName(); + } + Map overloadedMethodNamesMap = new HashMap<>(); + Map methodsRequestMappingsMap = new HashMap<>(); + for (Method m : overloadedMethods) { + overloadedMethodNamesMap.put(m, new StringBuilder(m.getName())); + methodsRequestMappingsMap.put(m, RequestMappingUtility.getRequestMapping(Arrays.asList (m.getDeclaredAnnotations()))); + } + + + appendHttpMethodToMethodName(overloadedMethodNamesMap, methodsRequestMappingsMap); + if(!methodNamesAreUnique(overloadedMethodNamesMap)){ + appendHttpPathToMethodName(overloadedMethodNamesMap, methodsRequestMappingsMap); + } + if(!methodNamesAreUnique(overloadedMethodNamesMap)){ + logger.error("There are overloaded REST methods which names are not unique after appending http method and http path : " + currentMethod); + } + overloadedMethodNamesMap.forEach((method, name) -> methodNamesMap.put(method, name)); + return overloadedMethodNamesMap.get(currentMethod).toString(); + } + + private void appendHttpMethodToMethodName(Map overloadedMethodNamesMap, Map methodsRequestMappingsMap) { + Map httpMethodsValuesMap = new HashMap<>(); + + for (Map.Entry entry : methodsRequestMappingsMap.entrySet()) { + Method key = entry.getKey(); + RequestMapping value = entry.getValue(); + SortedSet httpMethodsSet = new TreeSet<>(); + for (RequestMethod requestMethod : value.method()) { + httpMethodsSet.add(requestMethod.name()); + } + httpMethodsValuesMap.put(key, String.join("_", httpMethodsSet)); + } + + Set allDifferentHttpMethods = new HashSet<>(httpMethodsValuesMap.values()); + if (allDifferentHttpMethods.size() <= 1) { + return; + } + overloadedMethodNamesMap.forEach((method, methodName) -> methodName.append(httpMethodsValuesMap.get(method))); + } + + private void appendHttpPathToMethodName(Map overloadedMethodNamesMap, Map methodsRequestMappingsMap) { + Map methodsHttpPaths = new HashMap<>(); + for (Map.Entry entry : methodsRequestMappingsMap.entrySet()) { + Method method = entry.getKey(); + RequestMapping requestMapping = entry.getValue(); + if (requestMapping.path().length > 0) { + methodsHttpPaths.put(method, requestMapping.path()[0].split("/")); + }else{ + methodsHttpPaths.put(method, new String[]{""}); + } + } + + int startIndex = findPathCommonPrefixIndex(methodsHttpPaths); + + for (Method method : overloadedMethodNamesMap.keySet()) { + StringBuilder methodName = overloadedMethodNamesMap.get(method); + String[] pathComponents = methodsHttpPaths.get(method); + for (int i = startIndex; i < pathComponents.length; i++) { + String pathComponent = pathComponents[i]; + if(!pathComponent.contains("{") && !"".equals(pathComponent)) { + methodName.append("_"); + methodName.append(pathComponent.toUpperCase().replace("-", "_")); + } + } + } + + } + + int findPathCommonPrefixIndex(Map methodsHttpPaths) { + return 0; + } + + private boolean methodNamesAreUnique(Map overloadedMethodNamesMap) { + Set methodNames = new HashSet<>(); + overloadedMethodNamesMap.forEach((k, v) -> methodNames.add(v.toString())); + return overloadedMethodNamesMap.size() == methodNames.size(); + } + + private void setOptional(TSParameter tsParameter) { + for (Annotation annotation : tsParameter.getAnnotationList()) { + if(annotation instanceof PathVariable){ + PathVariable pathVariable = (PathVariable) annotation; + tsParameter.setOptional(!pathVariable.required()); + return; + } + if(annotation instanceof RequestParam){ + RequestParam requestParam = (RequestParam) annotation; + tsParameter.setOptional(!requestParam.required()); + if(!ValueConstants.DEFAULT_NONE.equals(requestParam.defaultValue())) { + tsParameter.setDefaultValue(requestParam.defaultValue()); + } + return; + } + if(annotation instanceof RequestBody) { + RequestBody requestBody = (RequestBody) annotation; + tsParameter.setOptional(!requestBody.required()); + return; + } + } + } + + private boolean parameterIsMapped(TSParameter tsParameter) { + for (Annotation annotation : tsParameter.getAnnotationList()) { + if(annotation instanceof PathVariable){ + return true; + } + if(annotation instanceof RequestParam){ + return true; + } + if(annotation instanceof RequestBody) { + return true; + } + } + + if (tsParameter.getType() instanceof TSInterfaceReference) { + TSInterfaceReference tsInterfaceReference = (TSInterfaceReference) tsParameter.getType(); + for (Class nextClass : tsInterfaceReference.getReferencedType().getMappedFromJavaTypeSet()) { + for (RestConversionExtension extension : getConversionExtensionList()) { + if (extension.isMappedRestParam(nextClass)) { + return true; + } + } + } + } + return false; + } + + protected boolean isRestMethod(Method method) { + if (method.isAnnotationPresent(RequestMapping.class)) { + return true; + } + for (Annotation annotation : method.getAnnotations()) { + if (annotation.annotationType().isAnnotationPresent(RequestMapping.class)) { + return true; + } + } + return false; + } + + private void setSupperClass(Class javaType, TSClass tsClass) { + TSType tsSupperClass = TypeMapper.map(javaType.getAnnotatedSuperclass().getType()); + if(tsSupperClass instanceof TSClassReference){ + TSClassReference tsClassReference = (TSClassReference) tsSupperClass; + convertFormalTypeParameters(javaType.getTypeParameters(), tsClassReference); + tsClass.setExtendsClass(tsClassReference); + tsClass.addScopedTypeUsage(tsClassReference); + } + } +} diff --git a/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/converters/SpringRestToTsConverter.java b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/converters/SpringRestToTsConverter.java index 6b65fef..9a244e0 100644 --- a/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/converters/SpringRestToTsConverter.java +++ b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/converters/SpringRestToTsConverter.java @@ -1,22 +1,13 @@ package com.blueveery.springrest2ts.converters; -import static com.blueveery.springrest2ts.spring.RequestMappingUtility.getRequestMapping; - -import com.blueveery.springrest2ts.extensions.RestConversionExtension; import com.blueveery.springrest2ts.implgens.ImplementationGenerator; import com.blueveery.springrest2ts.naming.ClassNameMapper; import com.blueveery.springrest2ts.tsmodel.*; -import com.blueveery.springrest2ts.tsmodel.generics.TSClassReference; -import com.blueveery.springrest2ts.tsmodel.generics.TSInterfaceReference; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import java.lang.annotation.Annotation; import java.lang.reflect.*; -import java.util.*; -import java.util.stream.Collectors; -public class SpringRestToTsConverter extends RestClassConverter{ +public class SpringRestToTsConverter extends SpringAnnotationsBasedRestClassConverter{ public SpringRestToTsConverter(ImplementationGenerator implementationGenerator) { super(implementationGenerator); @@ -27,273 +18,29 @@ public SpringRestToTsConverter(ImplementationGenerator implementationGenerator, } @Override - public boolean preConverted(JavaPackageToTsModuleConverter javaPackageToTsModuleConverter, Class javaClass){ - if(TypeMapper.map(javaClass) == TypeMapper.tsAny && !javaClass.isInterface()){ - TSModule tsModule = javaPackageToTsModuleConverter.getTsModule(javaClass); - String tsClassName = createTsClassName(javaClass); - TSClass tsClass = new TSClass(tsClassName, tsModule, implementationGenerator); - tsModule.addScopedType(tsClass); - TypeMapper.registerTsType(javaClass, tsClass); - return true; - } - return false; - } - - @Override - public void convert(Class javaClass, NullableTypesStrategy nullableTypesStrategy) { - TSClassReference tsClassReference = (TSClassReference) TypeMapper.map(javaClass); - TSClass tsClass = tsClassReference.getReferencedType(); - - convertFormalTypeParameters(javaClass.getTypeParameters(), tsClassReference); - setSupperClass(javaClass, tsClass); + protected void addClassAnnotations(Class javaClass, TSClass tsClass) { tsClass.addAllAnnotations(javaClass.getAnnotations()); - - TSMethod tsConstructorMethod = new TSMethod("constructor", tsClass, null, implementationGenerator, false, true); - tsClass.addTsMethod(tsConstructorMethod); - List restMethodList = filterRestMethods(javaClass); - Map methodNamesMap = new HashMap<>(); - - for (Method method: restMethodList) { - - Map variableNameToJavaType = new HashMap<>(); - Class declaringClass = method.getDeclaringClass(); - if(declaringClass != javaClass && method.getDeclaringClass().isInterface()){ - variableNameToJavaType = fillVariableNameToJavaType(javaClass, declaringClass); - } - - Type genericReturnType = method.getGenericReturnType(); - if (genericReturnType instanceof ParameterizedType) {// handling ResponseEntity - ParameterizedType parameterizedType = (ParameterizedType) genericReturnType; - if (parameterizedType.getRawType() == ResponseEntity.class) { - genericReturnType = parameterizedType.getActualTypeArguments()[0]; - } - } - String methodName = mapMethodName(restMethodList, methodNamesMap, method); - TSType methodReturnType = TypeMapper.map(resolveTypeVariable(genericReturnType, variableNameToJavaType)); - tsClass.getModule().scopedTypeUsage(methodReturnType); - TSMethod tsMethod = new TSMethod(methodName, tsClass, methodReturnType, implementationGenerator, false, false); - for (Parameter parameter:method.getParameters()) { - Type parameterType = resolveTypeVariable(parameter.getParameterizedType(), variableNameToJavaType); - TSParameter tsParameter = new TSParameter(parameter.getName(), TypeMapper.map(parameterType), tsMethod, implementationGenerator); - tsParameter.addAllAnnotations(parameter.getAnnotations()); - if (parameterIsMapped(tsParameter)) { - tsClass.getModule().scopedTypeUsage(tsParameter.getType()); - setOptional(tsParameter); - nullableTypesStrategy.setAsNullableType(parameter.getParameterizedType(), parameter.getDeclaredAnnotations(), tsParameter); - tsMethod.getParameterList().add(tsParameter); - } - conversionListener.tsParameterCreated(parameter, tsParameter); - } - tsMethod.addAllAnnotations(method.getAnnotations()); - tsClass.addTsMethod(tsMethod); - conversionListener.tsMethodCreated(method, tsMethod); - } - - implementationGenerator.addComplexTypeUsage(tsClass); - conversionListener.tsScopedTypeCreated(javaClass, tsClass); - } - - private Type resolveTypeVariable(Type type, Map variableNameToJavaType) { - if (type instanceof TypeVariable) { - TypeVariable typeVariable = (TypeVariable) type; - Type resolvedType = variableNameToJavaType.get(typeVariable.getName()); - if (resolvedType != null) { - return resolvedType; - } - } - return type; } - private Map fillVariableNameToJavaType(Class javaClass, Class declaringClass) { - Map typeParametersMap = new HashMap<>(); - for (Type type:javaClass.getGenericInterfaces()){ - if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - if (parameterizedType.getRawType() == declaringClass) { - for (int i = 0; i < parameterizedType.getActualTypeArguments().length; i++) { - TypeVariable typeParameter = declaringClass.getTypeParameters()[i]; - Type actualTypeArgument = parameterizedType.getActualTypeArguments()[i]; - typeParametersMap.put(typeParameter.getName(), actualTypeArgument); - } - return typeParametersMap; - } - return fillVariableNameToJavaType((Class) type, declaringClass); - } - } - return typeParametersMap; - } - - private List filterRestMethods(Class javaClass) { - List restMethodList = new ArrayList<>(); - for (Method method : javaClass.getMethods()) { - if(method.getDeclaringClass() == javaClass || method.getDeclaringClass().isInterface()) { - for (Method nextMethod : javaClass.getSuperclass().getMethods()) { - if(nextMethod.equals(method)){ - continue; // parent class contains method from interface - } - } - if (isRestMethod(method)) { - restMethodList.add(method); - } - } - } - return restMethodList; - } - - private String mapMethodName(List restMethodList, Map methodNamesMap, Method currentMethod) { - StringBuilder methodName = methodNamesMap.get(currentMethod); - if (methodName != null) { - return methodName.toString(); - } - List overloadedMethods = restMethodList.stream().filter(m -> m.getName().equals(currentMethod.getName())).collect(Collectors.toList()); - if (overloadedMethods.size() == 1) { - return currentMethod.getName(); - } - Map overloadedMethodNamesMap = new HashMap<>(); - Map methodsRequestMappingsMap = new HashMap<>(); - for (Method m : overloadedMethods) { - overloadedMethodNamesMap.put(m, new StringBuilder(m.getName())); - methodsRequestMappingsMap.put(m, getRequestMapping(Arrays.asList (m.getDeclaredAnnotations()))); - } - - - appendHttpMethodToMethodName(overloadedMethodNamesMap, methodsRequestMappingsMap); - if(!methodNamesAreUnique(overloadedMethodNamesMap)){ - appendHttpPathToMethodName(overloadedMethodNamesMap, methodsRequestMappingsMap); - } - if(!methodNamesAreUnique(overloadedMethodNamesMap)){ - logger.error("There are overloaded REST methods which names are not unique after appending http method and http path : " + currentMethod); - } - overloadedMethodNamesMap.forEach((method, name) -> methodNamesMap.put(method, name)); - return overloadedMethodNamesMap.get(currentMethod).toString(); - } - - private void appendHttpMethodToMethodName(Map overloadedMethodNamesMap, Map methodsRequestMappingsMap) { - Map httpMethodsValuesMap = new HashMap<>(); - - for (Map.Entry entry : methodsRequestMappingsMap.entrySet()) { - Method key = entry.getKey(); - RequestMapping value = entry.getValue(); - SortedSet httpMethodsSet = new TreeSet<>(); - for (RequestMethod requestMethod : value.method()) { - httpMethodsSet.add(requestMethod.name()); - } - httpMethodsValuesMap.put(key, String.join("_", httpMethodsSet)); - } - - Set allDifferentHttpMethods = new HashSet<>(httpMethodsValuesMap.values()); - if (allDifferentHttpMethods.size() <= 1) { - return; - } - overloadedMethodNamesMap.forEach((method, methodName) -> methodName.append(httpMethodsValuesMap.get(method))); - } - - private void appendHttpPathToMethodName(Map overloadedMethodNamesMap, Map methodsRequestMappingsMap) { - Map methodsHttpPaths = new HashMap<>(); - for (Map.Entry entry : methodsRequestMappingsMap.entrySet()) { - Method method = entry.getKey(); - RequestMapping requestMapping = entry.getValue(); - if (requestMapping.path().length > 0) { - methodsHttpPaths.put(method, requestMapping.path()[0].split("/")); - }else{ - methodsHttpPaths.put(method, new String[]{""}); - } - } - - int startIndex = findPathCommonPrefixIndex(methodsHttpPaths); - - for (Method method : overloadedMethodNamesMap.keySet()) { - StringBuilder methodName = overloadedMethodNamesMap.get(method); - String[] pathComponents = methodsHttpPaths.get(method); - for (int i = startIndex; i < pathComponents.length; i++) { - String pathComponent = pathComponents[i]; - if(!pathComponent.contains("{") && !"".equals(pathComponent)) { - methodName.append("_"); - methodName.append(pathComponent.toUpperCase().replace("-", "_")); - } - } - } - - } - - private int findPathCommonPrefixIndex(Map methodsHttpPaths) { - return 0; - } - - private boolean methodNamesAreUnique(Map overloadedMethodNamesMap) { - Set methodNames = new HashSet<>(); - overloadedMethodNamesMap.forEach((k, v) -> methodNames.add(v.toString())); - return overloadedMethodNamesMap.size() == methodNames.size(); - } - - private void setOptional(TSParameter tsParameter) { - for (Annotation annotation : tsParameter.getAnnotationList()) { - if(annotation instanceof PathVariable){ - PathVariable pathVariable = (PathVariable) annotation; - tsParameter.setOptional(!pathVariable.required()); - return; - } - if(annotation instanceof RequestParam){ - RequestParam requestParam = (RequestParam) annotation; - tsParameter.setOptional(!requestParam.required()); - if(!ValueConstants.DEFAULT_NONE.equals(requestParam.defaultValue())) { - tsParameter.setDefaultValue(requestParam.defaultValue()); - } - return; - } - if(annotation instanceof RequestBody) { - RequestBody requestBody = (RequestBody) annotation; - tsParameter.setOptional(!requestBody.required()); - return; - } - } + @Override + protected void addMethodAnnotations(Method method, TSMethod tsMethod) { + tsMethod.addAllAnnotations(method.getAnnotations()); } - private boolean parameterIsMapped(TSParameter tsParameter) { - for (Annotation annotation : tsParameter.getAnnotationList()) { - if(annotation instanceof PathVariable){ - return true; - } - if(annotation instanceof RequestParam){ - return true; - } - if(annotation instanceof RequestBody) { - return true; - } - } - - if (tsParameter.getType() instanceof TSInterfaceReference) { - TSInterfaceReference tsInterfaceReference = (TSInterfaceReference) tsParameter.getType(); - for (Class nextClass : tsInterfaceReference.getReferencedType().getMappedFromJavaTypeSet()) { - for (RestConversionExtension extension : getConversionExtensionList()) { - if (extension.isMappedRestParam(nextClass)) { - return true; - } - } - } - } - return false; + @Override + protected void addParameterAnnotations(Parameter parameter, TSParameter tsParameter) { + tsParameter.addAllAnnotations(parameter.getAnnotations()); } - private boolean isRestMethod(Method method) { - if (method.isAnnotationPresent(RequestMapping.class)) { - return true; - } - for (Annotation annotation : method.getAnnotations()) { - if (annotation.annotationType().isAnnotationPresent(RequestMapping.class)) { - return true; + @Override + protected Type handleImplementationSpecificReturnTypes(Method method) { + Type genericReturnType = method.getGenericReturnType(); + if (genericReturnType instanceof ParameterizedType) {// handling ResponseEntity + ParameterizedType parameterizedType = (ParameterizedType) genericReturnType; + if (parameterizedType.getRawType() == ResponseEntity.class) { + genericReturnType = parameterizedType.getActualTypeArguments()[findPathCommonPrefixIndex(null)]; } } - return false; - } - - private void setSupperClass(Class javaType, TSClass tsClass) { - TSType tsSupperClass = TypeMapper.map(javaType.getAnnotatedSuperclass().getType()); - if(tsSupperClass instanceof TSClassReference){ - TSClassReference tsClassReference = (TSClassReference) tsSupperClass; - convertFormalTypeParameters(javaType.getTypeParameters(), tsClassReference); - tsClass.setExtendsClass(tsClassReference); - tsClass.addScopedTypeUsage(tsClassReference); - } + return genericReturnType; } } diff --git a/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/MethodParameterEntity.java b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/MethodParameterEntity.java new file mode 100644 index 0000000..3a071b4 --- /dev/null +++ b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/MethodParameterEntity.java @@ -0,0 +1,31 @@ +package com.blueveery.springrest2ts.spring; + +public class MethodParameterEntity { + private String value; + private String name; + private boolean required; + + public String value() { + return value; + } + + public String name() { + return name; + } + + public boolean required() { + return required; + } + + public void setValue(String value) { + this.value = value; + } + + public void setName(String name) { + this.name = name; + } + + public void setRequired(boolean required) { + this.required = required; + } +} diff --git a/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/PathVariableEntity.java b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/PathVariableEntity.java new file mode 100644 index 0000000..dc83320 --- /dev/null +++ b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/PathVariableEntity.java @@ -0,0 +1,14 @@ +package com.blueveery.springrest2ts.spring; + +import org.springframework.web.bind.annotation.PathVariable; + +import java.lang.annotation.Annotation; + +public class PathVariableEntity extends MethodParameterEntity implements PathVariable { + + + @Override + public Class annotationType() { + return PathVariable.class; + } +} diff --git a/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/RequestBodyEntity.java b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/RequestBodyEntity.java new file mode 100644 index 0000000..d82a484 --- /dev/null +++ b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/RequestBodyEntity.java @@ -0,0 +1,23 @@ +package com.blueveery.springrest2ts.spring; + +import org.springframework.web.bind.annotation.RequestBody; + +import java.lang.annotation.Annotation; + +public class RequestBodyEntity implements RequestBody { + private boolean required; + + @Override + public boolean required() { + return required; + } + + public void setRequired(boolean required) { + this.required = required; + } + + @Override + public Class annotationType() { + return RequestBody.class; + } +} diff --git a/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/RequestParamEntity.java b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/RequestParamEntity.java new file mode 100644 index 0000000..b636c8d --- /dev/null +++ b/spring-rest2ts-spring/src/main/java/com/blueveery/springrest2ts/spring/RequestParamEntity.java @@ -0,0 +1,24 @@ +package com.blueveery.springrest2ts.spring; + +import org.springframework.web.bind.annotation.RequestParam; + +import java.lang.annotation.Annotation; + +public class RequestParamEntity extends MethodParameterEntity implements RequestParam { + + private String defaultValue; + + @Override + public String defaultValue() { + return defaultValue; + } + + public String getDefaultValue() { + return defaultValue; + } + + @Override + public Class annotationType() { + return RequestParam.class; + } +}