diff options
-rw-r--r-- | backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringRequestBodyBuilder.java | 98 | ||||
-rw-r--r-- | backend/src/main/java/org/luxons/sevenwonders/doc/scanner/JsonDocWebSocketScanner.java | 68 |
2 files changed, 143 insertions, 23 deletions
diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringRequestBodyBuilder.java b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringRequestBodyBuilder.java new file mode 100644 index 00000000..e3b20a81 --- /dev/null +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/SpringRequestBodyBuilder.java @@ -0,0 +1,98 @@ +package org.luxons.sevenwonders.doc.builders; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.security.Principal; +import java.util.Arrays; +import java.util.List; + +import org.jsondoc.core.pojo.ApiBodyObjectDoc; +import org.jsondoc.core.util.JSONDocType; +import org.jsondoc.core.util.JSONDocTypeBuilder; +import org.jsondoc.core.util.JSONDocUtils; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.messaging.handler.annotation.Headers; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.support.MessageHeaderAccessor; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +public class SpringRequestBodyBuilder { + + private static final List<Class<?>> NON_BODY_PARAM_TYPES = + Arrays.asList(MessageHeaders.class, MessageHeaderAccessor.class, Principal.class); + + private static final List<Class<? extends Annotation>> NON_BODY_PARAM_ANNOTATIONS = + Arrays.asList(Header.class, Headers.class, DestinationVariable.class); + + public static ApiBodyObjectDoc buildRequestBody(Method method) { + int index = getIndexOfBodyParam(method); + if (index < 0) { + return null; + } + final Class<?> bodyParamClass = method.getParameterTypes()[index]; + final Type bodyParamType = method.getGenericParameterTypes()[index]; + return new ApiBodyObjectDoc(JSONDocTypeBuilder.build(new JSONDocType(), bodyParamClass, bodyParamType)); + } + + public static int getIndexOfBodyParam(Method method) { + if (method.isAnnotationPresent(RequestMapping.class)) { + return getIndexOfBodyParamForRequestMapping(method); + } + if (method.isAnnotationPresent(MessageMapping.class)) { + return getIndexOfBodyParamForMessageMapping(method); + } + return -1; + } + + private static int getIndexOfBodyParamForRequestMapping(Method method) { + return JSONDocUtils.getIndexOfParameterWithAnnotation(method, RequestBody.class); + } + + private static int getIndexOfBodyParamForMessageMapping(Method method) { + Class<?>[] paramTypes = method.getParameterTypes(); + Annotation[][] paramAnnotations = method.getParameterAnnotations(); + for (int i = 0; i < paramTypes.length; i++) { + if (isBodyParam(paramTypes[i], paramAnnotations[i])) { + return i; + } + } + // no body param found + return -1; + } + + private static boolean isBodyParam(Class<?> paramType, Annotation[] paramAnnotations) { + if (paramType.equals(Message.class)) { + // it is too generic to be useful in the API doc even if it is indeed the body param + return false; + } + if (NON_BODY_PARAM_TYPES.contains(paramType)) { + return false; + } + for (Annotation paramAnnotation : paramAnnotations) { + // guarantees this param is the payload + if (paramAnnotation instanceof Payload) { + return true; + } + // guarantees this param is NOT the payload by definition of the annotation + if (isNonBodyParamAnnotation(paramAnnotation)) { + return false; + } + } + return true; + } + + private static boolean isNonBodyParamAnnotation(Annotation paramAnnotation) { + for (Class<? extends Annotation> annotation : NON_BODY_PARAM_ANNOTATIONS) { + if (annotation.isInstance(paramAnnotation)) { + return true; + } + } + return false; + } +} diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/scanner/JsonDocWebSocketScanner.java b/backend/src/main/java/org/luxons/sevenwonders/doc/scanner/JsonDocWebSocketScanner.java index 7dc0854e..f305a8ac 100644 --- a/backend/src/main/java/org/luxons/sevenwonders/doc/scanner/JsonDocWebSocketScanner.java +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/scanner/JsonDocWebSocketScanner.java @@ -2,6 +2,8 @@ package org.luxons.sevenwonders.doc.scanner; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -13,6 +15,7 @@ import com.google.common.collect.Sets; import org.jsondoc.core.annotation.Api; import org.jsondoc.core.annotation.ApiMethod; import org.jsondoc.core.pojo.ApiMethodDoc; +import org.jsondoc.core.pojo.JSONDoc; import org.jsondoc.core.pojo.JSONDocTemplate; import org.jsondoc.core.scanner.builder.JSONDocApiMethodDocBuilder; import org.jsondoc.core.util.JSONDocUtils; @@ -22,12 +25,11 @@ import org.jsondoc.springmvc.scanner.builder.SpringHeaderBuilder; import org.jsondoc.springmvc.scanner.builder.SpringPathVariableBuilder; import org.jsondoc.springmvc.scanner.builder.SpringProducesBuilder; import org.jsondoc.springmvc.scanner.builder.SpringQueryParamBuilder; -import org.jsondoc.springmvc.scanner.builder.SpringRequestBodyBuilder; import org.jsondoc.springmvc.scanner.builder.SpringResponseBuilder; import org.jsondoc.springmvc.scanner.builder.SpringResponseStatusBuilder; import org.jsondoc.springmvc.scanner.builder.SpringVerbBuilder; import org.luxons.sevenwonders.doc.builders.SpringPathBuilder; -import org.reflections.Reflections; +import org.luxons.sevenwonders.doc.builders.SpringRequestBodyBuilder; import org.springframework.beans.BeanUtils; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.simp.annotation.SubscribeMapping; @@ -88,35 +90,47 @@ public class JsonDocWebSocketScanner extends Spring4JSONDocScanner { @Override public Set<Class<?>> jsondocObjects(List<String> packages) { - Set<Method> methodsToDocument = getMethodsToDocument(); - - Set<Class<?>> candidates = Sets.newHashSet(); + Set<Class<?>> candidates = getRootApiObjects(); Set<Class<?>> subCandidates = Sets.newHashSet(); - for (Method method : methodsToDocument) { - buildJSONDocObjectsCandidates(candidates, method.getReturnType(), method.getGenericReturnType(), - reflections); - Integer requestBodyParameterIndex = - JSONDocUtils.getIndexOfParameterWithAnnotation(method, RequestBody.class); - if (requestBodyParameterIndex != -1) { - candidates.addAll( - buildJSONDocObjectsCandidates(candidates, method.getParameterTypes()[requestBodyParameterIndex], - method.getGenericParameterTypes()[requestBodyParameterIndex], reflections)); - } - } - // This is to get objects' fields that are not returned nor part of the body request of a method, but that - // are a field - // of an object returned or a body of a request of a method + // are a field of an object returned or a body of a request of a method for (Class<?> clazz : candidates) { - appendSubCandidates(clazz, subCandidates, reflections); + appendSubCandidates(clazz, subCandidates); } - candidates.addAll(subCandidates); return candidates.stream().filter(clazz -> inWhiteListedPackages(packages, clazz)).collect(Collectors.toSet()); } + private Set<Class<?>> getRootApiObjects() { + Set<Class<?>> candidates = Sets.newHashSet(); + Set<Method> methodsToDocument = getMethodsToDocument(); + for (Method method : methodsToDocument) { + addReturnType(candidates, method); + addBodyParam(candidates, method); + } + return candidates; + } + + private void addReturnType(Set<Class<?>> candidates, Method method) { + Class<?> returnValueClass = method.getReturnType(); + if (returnValueClass.isPrimitive() || returnValueClass.equals(JSONDoc.class)) { + return; + } + buildJSONDocObjectsCandidates(candidates, returnValueClass, method.getGenericReturnType(), + reflections); + } + + private void addBodyParam(Set<Class<?>> candidates, Method method) { + int bodyParamIndex = SpringRequestBodyBuilder.getIndexOfBodyParam(method); + if (bodyParamIndex >= 0) { + Class<?> bodyParamClass = method.getParameterTypes()[bodyParamIndex]; + Type bodyParamType = method.getGenericParameterTypes()[bodyParamIndex]; + buildJSONDocObjectsCandidates(candidates, bodyParamClass, bodyParamType, reflections); + } + } + private Set<Method> getMethodsToDocument() { Set<Method> methodsAnnotatedWith = reflections.getMethodsAnnotatedWith(RequestMapping.class); methodsAnnotatedWith.addAll(reflections.getMethodsAnnotatedWith(SubscribeMapping.class)); @@ -129,12 +143,16 @@ public class JsonDocWebSocketScanner extends Spring4JSONDocScanner { return p != null && packages.stream().anyMatch(whiteListedPkg -> p.getName().startsWith(whiteListedPkg)); } - private void appendSubCandidates(Class<?> clazz, Set<Class<?>> subCandidates, Reflections reflections) { + private void appendSubCandidates(Class<?> clazz, Set<Class<?>> subCandidates) { if (clazz.isPrimitive() || clazz.equals(Class.class)) { return; } for (Field field : clazz.getDeclaredFields()) { + if (!isValidForRecursion(field)) { + continue; + } + Class<?> fieldClass = field.getType(); Set<Class<?>> fieldCandidates = new HashSet<>(); buildJSONDocObjectsCandidates(fieldCandidates, fieldClass, field.getGenericType(), reflections); @@ -143,9 +161,13 @@ public class JsonDocWebSocketScanner extends Spring4JSONDocScanner { if (!subCandidates.contains(candidate)) { subCandidates.add(candidate); - appendSubCandidates(candidate, subCandidates, reflections); + appendSubCandidates(candidate, subCandidates); } } } } + + private boolean isValidForRecursion(Field field) { + return !field.isSynthetic() && !field.getType().isPrimitive() && !Modifier.isTransient(field.getModifiers()); + } } |