diff options
3 files changed, 187 insertions, 2 deletions
diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/JsonDocController.java b/backend/src/main/java/org/luxons/sevenwonders/doc/JsonDocController.java index 11725150..e9edde3e 100644 --- a/backend/src/main/java/org/luxons/sevenwonders/doc/JsonDocController.java +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/JsonDocController.java @@ -36,7 +36,7 @@ public class JsonDocController { this.version = "1.0.0"; this.basePath = "http://localhost:8080"; this.packages = Arrays.asList("org.luxons.sevenwonders.controllers", "org.luxons.sevenwonders.doc", - "org.luxons.sevenwonders.actions", "org.luxons.sevenwonders.game"); + "org.luxons.sevenwonders.actions", "org.luxons.sevenwonders.game", "org.luxons.sevenwonders.lobby"); this.jsondocScanner = new JsonDocWebSocketScanner(); } diff --git a/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocTemplateBuilder.java b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocTemplateBuilder.java new file mode 100644 index 00000000..78c86584 --- /dev/null +++ b/backend/src/main/java/org/luxons/sevenwonders/doc/builders/JSONDocTemplateBuilder.java @@ -0,0 +1,137 @@ +package org.luxons.sevenwonders.doc.builders; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.jsondoc.core.annotation.ApiObjectField; +import org.jsondoc.core.pojo.JSONDocTemplate; +import org.jsondoc.core.util.JSONDocFieldWrapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JSONDocTemplateBuilder { + + private static final Logger log = LoggerFactory.getLogger(org.jsondoc.core.util.JSONDocTemplateBuilder.class); + private static final Map<Class<?>, Class<?>> primitives = new HashMap<Class<?>, Class<?>>(); + + static { + primitives.put(boolean.class, Boolean.class); + primitives.put(byte.class, Byte.class); + primitives.put(char.class, String.class); + primitives.put(double.class, Double.class); + primitives.put(float.class, Float.class); + primitives.put(int.class, Integer.class); + primitives.put(long.class, Long.class); + primitives.put(short.class, Short.class); + primitives.put(void.class, Void.class); + } + + public static JSONDocTemplate build(Class<?> clazz, Set<Class<?>> jsondocObjects) { + final JSONDocTemplate jsonDocTemplate = new JSONDocTemplate(); + + if(jsondocObjects.contains(clazz)) { + try { + Set<JSONDocFieldWrapper> fields = getAllDeclaredFields(clazz); + + for (JSONDocFieldWrapper jsondocFieldWrapper : fields) { + Field field = jsondocFieldWrapper.getField(); + String fieldName = field.getName(); + ApiObjectField apiObjectField = field.getAnnotation(ApiObjectField.class); + if (apiObjectField != null && !apiObjectField.name().isEmpty()) { + fieldName = apiObjectField.name(); + } + + Object value; + // This condition is to avoid StackOverflow in case class "A" + // contains a field of type "A" + if (field.getType().equals(clazz) || (apiObjectField != null && !apiObjectField.processtemplate())) { + value = getValue(Object.class, field.getGenericType(), fieldName, jsondocObjects); + } else { + value = getValue(field.getType(), field.getGenericType(), fieldName, jsondocObjects); + } + + jsonDocTemplate.put(fieldName, value); + } + + } catch (Exception e) { + log.error("Error in JSONDocTemplate creation for class [" + clazz.getCanonicalName() + "]", e); + } + } + + return jsonDocTemplate; + } + + private static Object getValue(Class<?> fieldClass, Type fieldGenericType, String fieldName, Set<Class<?>> jsondocObjects) { + + if (fieldClass.isPrimitive()) { + return getValue(wrap(fieldClass), null, fieldName, jsondocObjects); + + } else if (Map.class.isAssignableFrom(fieldClass)) { + return new HashMap<Object, Object>(); + + } else if (Number.class.isAssignableFrom(fieldClass)) { + return new Integer(0); + + } else if (String.class.isAssignableFrom(fieldClass) || fieldClass.isEnum()) { + return new String(""); + + } else if (Boolean.class.isAssignableFrom(fieldClass)) { + return new Boolean("true"); + + } else if (fieldClass.isArray() || Collection.class.isAssignableFrom(fieldClass)) { + return new ArrayList<Object>(); + + } else { + return build(fieldClass, jsondocObjects); + } + + } + + private static Set<JSONDocFieldWrapper> getAllDeclaredFields(Class<?> clazz) { + Set<JSONDocFieldWrapper> fields = new TreeSet<JSONDocFieldWrapper>(); + + List<Field> declaredFields = new ArrayList<Field>(); + if (clazz.isEnum()) { + return fields; + } else { + declaredFields.addAll(Arrays.asList(clazz.getDeclaredFields())); + } + + for (Field field : declaredFields) { + if (!shouldBeSerialized(field)) { + continue; + } + if (field.isAnnotationPresent(ApiObjectField.class)) { + ApiObjectField annotation = field.getAnnotation(ApiObjectField.class); + fields.add(new JSONDocFieldWrapper(field, annotation.order())); + } else { + fields.add(new JSONDocFieldWrapper(field, Integer.MAX_VALUE)); + } + } + + if (clazz.getSuperclass() != null) { + fields.addAll(getAllDeclaredFields(clazz.getSuperclass())); + } + + return fields; + } + + private static boolean shouldBeSerialized(Field field) { + return !field.isSynthetic() && !Modifier.isTransient(field.getModifiers()); + } + + @SuppressWarnings("unchecked") + private static <T> Class<T> wrap(Class<T> clazz) { + return clazz.isPrimitive() ? (Class<T>) primitives.get(clazz) : clazz; + } + +} 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 f305a8ac..0f5f5557 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 @@ -4,6 +4,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; +import java.net.URL; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -16,6 +17,7 @@ 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.JSONDoc.MethodDisplay; import org.jsondoc.core.pojo.JSONDocTemplate; import org.jsondoc.core.scanner.builder.JSONDocApiMethodDocBuilder; import org.jsondoc.core.util.JSONDocUtils; @@ -28,8 +30,14 @@ import org.jsondoc.springmvc.scanner.builder.SpringQueryParamBuilder; 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.JSONDocTemplateBuilder; import org.luxons.sevenwonders.doc.builders.SpringPathBuilder; import org.luxons.sevenwonders.doc.builders.SpringRequestBodyBuilder; +import org.reflections.Reflections; +import org.reflections.scanners.MethodAnnotationsScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.reflections.util.FilterBuilder; import org.springframework.beans.BeanUtils; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.simp.annotation.SubscribeMapping; @@ -38,6 +46,46 @@ import org.springframework.web.bind.annotation.RequestMapping; public class JsonDocWebSocketScanner extends Spring4JSONDocScanner { + /** + * Returns the main <code>ApiDoc</code>, containing <code>ApiMethodDoc</code> and <code>ApiObjectDoc</code> objects + * @return An <code>ApiDoc</code> object + */ + public JSONDoc getJSONDoc(String version, String basePath, List<String> packages, boolean playgroundEnabled, MethodDisplay displayMethodAs) { + Set<URL> urls = new HashSet<URL>(); + FilterBuilder filter = new FilterBuilder(); + + log.debug("Found " + packages.size() + " package(s) to scan..."); + for (String pkg : packages) { + log.debug("Adding package to JSONDoc recursive scan: " + pkg); + urls.addAll(ClasspathHelper.forPackage(pkg)); + filter.includePackage(pkg); + } + + reflections = new Reflections(new ConfigurationBuilder().filterInputsBy(filter).setUrls(urls).addScanners(new MethodAnnotationsScanner())); + + JSONDoc jsondocDoc = new JSONDoc(version, basePath); + jsondocDoc.setPlaygroundEnabled(playgroundEnabled); + jsondocDoc.setDisplayMethodAs(displayMethodAs); + + jsondocControllers = jsondocControllers(); + jsondocObjects = jsondocObjects(packages); + jsondocFlows = jsondocFlows(); + jsondocGlobal = jsondocGlobal(); + jsondocChangelogs = jsondocChangelogs(); + jsondocMigrations = jsondocMigrations(); + + for (Class<?> clazz : jsondocObjects) { + jsondocTemplates.put(clazz, JSONDocTemplateBuilder.build(clazz, jsondocObjects)); + } + + jsondocDoc.setApis(getApiDocsMap(jsondocControllers, displayMethodAs)); + jsondocDoc.setObjects(getApiObjectsMap(jsondocObjects)); + jsondocDoc.setFlows(getApiFlowDocsMap(jsondocFlows, allApiMethodDocs)); + jsondocDoc.setGlobal(getApiGlobalDoc(jsondocGlobal, jsondocChangelogs, jsondocMigrations)); + + return jsondocDoc; + } + @Override public Set<Method> jsondocMethods(Class<?> controller) { Set<Method> annotatedMethods = new LinkedHashSet<Method>(); @@ -167,7 +215,7 @@ public class JsonDocWebSocketScanner extends Spring4JSONDocScanner { } } - private boolean isValidForRecursion(Field field) { + private static boolean isValidForRecursion(Field field) { return !field.isSynthetic() && !field.getType().isPrimitive() && !Modifier.isTransient(field.getModifiers()); } } |