package org.jsondoc.springmvc.scanner; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.jsondoc.core.annotation.Api; import org.jsondoc.core.annotation.ApiMethod; import org.jsondoc.core.annotation.ApiObject; import org.jsondoc.core.annotation.flow.ApiFlowSet; import org.jsondoc.core.annotation.global.ApiChangelogSet; import org.jsondoc.core.annotation.global.ApiGlobal; import org.jsondoc.core.annotation.global.ApiMigrationSet; import org.jsondoc.core.pojo.ApiDoc; import org.jsondoc.core.pojo.ApiMethodDoc; import org.jsondoc.core.pojo.ApiObjectDoc; import org.jsondoc.core.pojo.JSONDocTemplate; import org.jsondoc.core.scanner.AbstractJSONDocScanner; import org.jsondoc.core.scanner.builder.JSONDocApiDocBuilder; import org.jsondoc.core.scanner.builder.JSONDocApiMethodDocBuilder; import org.jsondoc.core.scanner.builder.JSONDocApiObjectDocBuilder; import org.jsondoc.core.util.JSONDocUtils; import org.jsondoc.springmvc.scanner.builder.SpringConsumesBuilder; import org.jsondoc.springmvc.scanner.builder.SpringHeaderBuilder; import org.jsondoc.springmvc.scanner.builder.SpringObjectBuilder; import org.jsondoc.springmvc.scanner.builder.SpringPathBuilder; 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.reflections.Reflections; import org.springframework.beans.BeanUtils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import com.google.common.collect.Sets; public abstract class AbstractSpringJSONDocScanner extends AbstractJSONDocScanner { @Override public Set<Method> jsondocMethods(Class<?> controller) { Set<Method> annotatedMethods = new LinkedHashSet<Method>(); for (Method method : controller.getDeclaredMethods()) { if (method.isAnnotationPresent(RequestMapping.class)) { annotatedMethods.add(method); } } return annotatedMethods; } /** * Returns a set of classes that are either return types or body objects * @param candidates * @param clazz * @param type * @param reflections * @return */ public static Set<Class<?>> buildJSONDocObjectsCandidates(Set<Class<?>> candidates, Class<?> clazz, Type type, Reflections reflections) { if (Map.class.isAssignableFrom(clazz)) { if (type instanceof ParameterizedType) { Type mapKeyType = ((ParameterizedType) type).getActualTypeArguments()[0]; Type mapValueType = ((ParameterizedType) type).getActualTypeArguments()[1]; if (mapKeyType instanceof Class) { candidates.add((Class<?>) mapKeyType); } else if (mapKeyType instanceof WildcardType) { candidates.add(Void.class); } else { if (mapKeyType instanceof ParameterizedType) { candidates.addAll(buildJSONDocObjectsCandidates(candidates, (Class<?>) ((ParameterizedType) mapKeyType).getRawType(), mapKeyType, reflections)); } } if (mapValueType instanceof Class) { candidates.add((Class<?>) mapValueType); } else if (mapValueType instanceof WildcardType) { candidates.add(Void.class); } else { if (mapValueType instanceof ParameterizedType) { candidates.addAll(buildJSONDocObjectsCandidates(candidates, (Class<?>) ((ParameterizedType) mapValueType).getRawType(), mapValueType, reflections)); } } } } else if (Collection.class.isAssignableFrom(clazz)) { if (type instanceof ParameterizedType) { Type parametrizedType = ((ParameterizedType) type).getActualTypeArguments()[0]; candidates.add(clazz); if (parametrizedType instanceof Class) { candidates.add((Class<?>) parametrizedType); } else if (parametrizedType instanceof WildcardType) { candidates.add(Void.class); } else { candidates.addAll(buildJSONDocObjectsCandidates(candidates, (Class<?>) ((ParameterizedType) parametrizedType).getRawType(), parametrizedType, reflections)); } } else if (type instanceof GenericArrayType) { candidates.addAll(buildJSONDocObjectsCandidates(candidates, clazz, ((GenericArrayType) type).getGenericComponentType(), reflections)); } else { candidates.add(clazz); } } else if (clazz.isArray()) { Class<?> componentType = clazz.getComponentType(); candidates.addAll(buildJSONDocObjectsCandidates(candidates, componentType, type, reflections)); } else { if (type instanceof ParameterizedType) { Type parametrizedType = ((ParameterizedType) type).getActualTypeArguments()[0]; if (parametrizedType instanceof Class) { Class<?> candidate = (Class<?>) parametrizedType; if(candidate.isInterface()) { for (Class<?> implementation : reflections.getSubTypesOf(candidate)) { buildJSONDocObjectsCandidates(candidates, implementation, parametrizedType, reflections); } } else { candidates.add(candidate); candidates.addAll(buildJSONDocObjectsCandidates(candidates, (Class<?>) ((ParameterizedType) type).getRawType(), parametrizedType, reflections)); } } else if (parametrizedType instanceof WildcardType) { candidates.add(Void.class); candidates.addAll(buildJSONDocObjectsCandidates(candidates, (Class<?>) ((ParameterizedType) type).getRawType(), parametrizedType, reflections)); } else if(parametrizedType instanceof TypeVariable<?>) { candidates.add(Void.class); candidates.addAll(buildJSONDocObjectsCandidates(candidates, (Class<?>) ((ParameterizedType) type).getRawType(), parametrizedType, reflections)); } else { candidates.addAll(buildJSONDocObjectsCandidates(candidates, (Class<?>) ((ParameterizedType) parametrizedType).getRawType(), parametrizedType, reflections)); } } else if(clazz.isInterface()) { for (Class<?> implementation : reflections.getSubTypesOf(clazz)) { candidates.addAll(buildJSONDocObjectsCandidates(candidates, implementation, type, reflections)); } } else { candidates.add(clazz); } } return candidates; } private void appendSubCandidates(Class<?> clazz, Set<Class<?>> subCandidates, Reflections reflections) { if(clazz.isPrimitive() || clazz.equals(Class.class)) { return; } for (Field field : clazz.getDeclaredFields()) { Class<?> fieldClass = field.getType(); Set<Class<?>> fieldCandidates = new HashSet<Class<?>>(); buildJSONDocObjectsCandidates(fieldCandidates, fieldClass, field.getGenericType(), reflections); for(Class<?> candidate: fieldCandidates) { if(!subCandidates.contains(candidate)) { subCandidates.add(candidate); appendSubCandidates(candidate, subCandidates, reflections); } } } } @Override public Set<Class<?>> jsondocObjects(List<String> packages) { Set<Method> methodsAnnotatedWith = reflections.getMethodsAnnotatedWith(RequestMapping.class); Set<Class<?>> candidates = Sets.newHashSet(); Set<Class<?>> subCandidates = Sets.newHashSet(); Set<Class<?>> elected = Sets.newHashSet(); for (Method method : methodsAnnotatedWith) { 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 for (Class<?> clazz : candidates) { appendSubCandidates(clazz, subCandidates, reflections); } candidates.addAll(subCandidates); for (Class<?> clazz : candidates) { if(clazz.getPackage() != null) { for (String pkg : packages) { if(clazz.getPackage().getName().contains(pkg)) { elected.add(clazz); } } } } return elected; } @Override public Set<Class<?>> jsondocFlows() { return reflections.getTypesAnnotatedWith(ApiFlowSet.class, true); } /** * ApiDoc is initialized with the Controller's simple class name. */ @Override public ApiDoc initApiDoc(Class<?> controller) { ApiDoc apiDoc = new ApiDoc(); apiDoc.setName(controller.getSimpleName()); apiDoc.setDescription(controller.getSimpleName()); return apiDoc; } /** * Once the ApiDoc has been initialized and filled with other data (version, * auth, etc) it's time to merge the documentation with JSONDoc annotation, * if existing. */ @Override public ApiDoc mergeApiDoc(Class<?> controller, ApiDoc apiDoc) { ApiDoc jsondocApiDoc = JSONDocApiDocBuilder.build(controller); BeanUtils.copyProperties(jsondocApiDoc, apiDoc, new String[] { "methods", "supportedversions", "auth" }); return apiDoc; } @Override public ApiMethodDoc initApiMethodDoc(Method method, Map<Class<?>, JSONDocTemplate> jsondocTemplates) { ApiMethodDoc apiMethodDoc = new ApiMethodDoc(); apiMethodDoc.setPath(SpringPathBuilder.buildPath(method)); apiMethodDoc.setMethod(method.getName()); apiMethodDoc.setVerb(SpringVerbBuilder.buildVerb(method)); apiMethodDoc.setProduces(SpringProducesBuilder.buildProduces(method)); apiMethodDoc.setConsumes(SpringConsumesBuilder.buildConsumes(method)); apiMethodDoc.setHeaders(SpringHeaderBuilder.buildHeaders(method)); apiMethodDoc.setPathparameters(SpringPathVariableBuilder.buildPathVariable(method)); apiMethodDoc.setQueryparameters(SpringQueryParamBuilder.buildQueryParams(method)); apiMethodDoc.setBodyobject(SpringRequestBodyBuilder.buildRequestBody(method)); apiMethodDoc.setResponse(SpringResponseBuilder.buildResponse(method)); apiMethodDoc.setResponsestatuscode(SpringResponseStatusBuilder.buildResponseStatusCode(method)); Integer index = JSONDocUtils.getIndexOfParameterWithAnnotation(method, RequestBody.class); if (index != -1) { apiMethodDoc.getBodyobject().setJsondocTemplate(jsondocTemplates.get(method.getParameterTypes()[index])); } return apiMethodDoc; } @Override public ApiMethodDoc mergeApiMethodDoc(Method method, ApiMethodDoc apiMethodDoc) { if (method.isAnnotationPresent(ApiMethod.class) && method.getDeclaringClass().isAnnotationPresent(Api.class)) { ApiMethodDoc jsondocApiMethodDoc = JSONDocApiMethodDocBuilder.build(method); BeanUtils.copyProperties(jsondocApiMethodDoc, apiMethodDoc, new String[] { "path", "verb", "produces", "consumes", "headers", "pathparameters", "queryparameters", "bodyobject", "response", "responsestatuscode", "apierrors", "supportedversions", "auth", "displayMethodAs" }); } return apiMethodDoc; } @Override public ApiObjectDoc initApiObjectDoc(Class<?> clazz) { return SpringObjectBuilder.buildObject(clazz); } @Override public ApiObjectDoc mergeApiObjectDoc(Class<?> clazz, ApiObjectDoc apiObjectDoc) { if(clazz.isAnnotationPresent(ApiObject.class)) { ApiObjectDoc jsondocApiObjectDoc = JSONDocApiObjectDocBuilder.build(clazz); BeanUtils.copyProperties(jsondocApiObjectDoc, apiObjectDoc); } return apiObjectDoc; } @Override public Set<Class<?>> jsondocGlobal() { return reflections.getTypesAnnotatedWith(ApiGlobal.class, true); } @Override public Set<Class<?>> jsondocChangelogs() { return reflections.getTypesAnnotatedWith(ApiChangelogSet.class, true); } @Override public Set<Class<?>> jsondocMigrations() { return reflections.getTypesAnnotatedWith(ApiMigrationSet.class, true); } }