package io.asfjava.ui.core.schema; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import org.reflections.ReflectionUtils; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.module.jsonSchema.JsonSchema; import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator; import io.asfjava.ui.core.FormDefinitionGeneratorFactory; import io.asfjava.ui.core.form.Action; import io.asfjava.ui.core.form.ActionsGroup; import io.asfjava.ui.core.form.FieldSet; import io.asfjava.ui.core.form.Index; import io.asfjava.ui.core.form.Tab; import io.asfjava.ui.dto.UiForm; public final class UiFormSchemaGenerator { private static final String KEY_FIELDSET = "fieldset"; private static final String KEY_ON_CLICK = "onClick"; private static final String KEY_ACTIONS = "actions"; private static final String KEY_TABS = "tabs"; private static final String KEY_TITLE = "title"; private static final String KEY_TYPE = "type"; private static final String KEY_ITEMS = "items"; private static UiFormSchemaGenerator instance; public UiForm generate(Class<? extends Serializable> formDto) throws JsonMappingException { Set<Field> declaredFields = ReflectionUtils.getAllFields(formDto, field -> !Modifier.isStatic(field.getModifiers())); ObjectMapper mapper = new ObjectMapper(); JsonSchemaGenerator schemaGen = initSchemaGen(mapper); JsonSchema schema = generateSchema(formDto, schemaGen); Map<Field, JsonNode> nodes = initFieldsFormDefinition(mapper, declaredFields); Map<Field, JsonNode> sortedNodes = reorderFieldsBasedOnIndex(nodes); handlerGroupedFields(mapper, declaredFields, sortedNodes); Optional<ObjectNode> tabbedFields = Optional .ofNullable(handleTabbedFields(mapper, declaredFields, sortedNodes)); ArrayNode formDefinition = mapper.createArrayNode(); tabbedFields.ifPresent(formDefinition::add); sortedNodes.entrySet().stream().forEach(nodesElement -> formDefinition.add(nodesElement.getValue())); handleActionsAnnotation(mapper, formDto, formDefinition); return new UiForm(schema, formDefinition); } private void handleActionsAnnotation(ObjectMapper mapper, Class<? extends Serializable> formDto, ArrayNode formDefinition) { ObjectNode groupedActionsNode = mapper.createObjectNode(); buildActions(mapper, formDto, formDefinition); buildGroupedActions(mapper, formDto, groupedActionsNode, formDefinition); } private void buildActions(ObjectMapper mapper, Class<? extends Serializable> formDto, ArrayNode formDefinition) { Action[] actionsAnnotations = formDto.getAnnotationsByType(Action.class); Arrays.stream(actionsAnnotations).forEach(action -> formDefinition.add(buildActionNode(mapper, action))); } private void buildGroupedActions(ObjectMapper mapper, Class<? extends Serializable> formDto, ObjectNode actionsNode, ArrayNode formDefinition) { Optional<ActionsGroup> actionsAnnotation = Optional.ofNullable(formDto.getAnnotation(ActionsGroup.class)); actionsAnnotation.ifPresent(actions -> { actionsNode.put(KEY_TYPE, KEY_ACTIONS); ArrayNode items = mapper.createArrayNode(); Arrays.stream(actions.value()).forEach(action -> { ObjectNode node = buildActionNode(mapper, action); items.add(node); }); actionsNode.set(KEY_ITEMS, items); formDefinition.add(actionsNode); }); } private ObjectNode buildActionNode(ObjectMapper mapper, Action action) { ObjectNode node = mapper.createObjectNode(); node.put(KEY_TYPE, action.type()); node.put(KEY_TITLE, action.title()); node.put(KEY_ON_CLICK, action.onClick()); return node; } private ObjectNode handlerGroupedFields(ObjectMapper mapper, Set<Field> declaredFields, Map<Field, JsonNode> sortedNodes) { Predicate<? super Field> checkFieldSetAnnotation = field -> field.isAnnotationPresent(FieldSet.class); Map<String, List<JsonNode>> groupedFields = new LinkedHashMap<>(); declaredFields.stream().filter(checkFieldSetAnnotation) .forEach(field -> groupFieldsByTab(sortedNodes, field, groupedFields)); ArrayNode groups = mapper.createArrayNode(); ObjectNode tabsNode = mapper.createObjectNode(); tabsNode.put(KEY_TYPE, KEY_FIELDSET); tabsNode.set(KEY_ITEMS, groups); return tabsNode; } private ObjectNode handleTabbedFields(ObjectMapper mapper, Set<Field> declaredFields, Map<Field, JsonNode> nodes) { Predicate<? super Field> checkTabAnnotation = field -> field.isAnnotationPresent(Tab.class); Comparator<? super Field> tabIndexComparator = (field1, field2) -> Integer .compare(field1.getAnnotation(Tab.class).index(), field2.getAnnotation(Tab.class).index()); Comparator<? super Field> fieldIndexComparator = (entry1, entry2) -> { Index field1Index = entry1.getAnnotation(Index.class); Index field2Index = entry2.getAnnotation(Index.class); return Integer.compare((field1Index != null ? field1Index.value() : Integer.MAX_VALUE), field2Index != null ? field2Index.value() : Integer.MAX_VALUE); }; Map<String, List<JsonNode>> groupedFieldsByTab = new LinkedHashMap<>(); declaredFields.stream().filter(checkTabAnnotation).sorted(fieldIndexComparator).sorted(tabIndexComparator) .forEach(field -> groupFieldsByTab(nodes, field, groupedFieldsByTab)); ArrayNode tabs = mapper.createArrayNode(); groupedFieldsByTab.entrySet().stream().forEachOrdered(tabElements -> { ObjectNode tabNode = mapper.createObjectNode(); tabNode.put(KEY_TITLE, tabElements.getKey()); ArrayNode tabItems = mapper.createArrayNode(); tabElements.getValue().stream().forEach(tabItems::add); tabNode.set(KEY_ITEMS, tabItems); tabs.add(tabNode); }); if (tabs.size() > 0) { ObjectNode tabsNode = mapper.createObjectNode(); tabsNode.put(KEY_TYPE, KEY_TABS); tabsNode.set(KEY_TABS, tabs); return tabsNode; } return null; } private Map<Field, JsonNode> initFieldsFormDefinition(ObjectMapper mapper, Set<Field> declaredFields) { Map<Field, JsonNode> nodes = new HashMap<>(); declaredFields.forEach(field -> buildFormDefinition(nodes, mapper, field)); return nodes; } private JsonSchema generateSchema(Class<? extends Serializable> formDto, JsonSchemaGenerator schemaGen) throws JsonMappingException { return schemaGen.generateSchema(formDto); } private JsonSchemaGenerator initSchemaGen(ObjectMapper mapper) { return new JsonSchemaGenerator(mapper, new CustomSchemaFactoryWrapper()); } private void groupFieldsByTab(Map<Field, JsonNode> nodes, Field field, Map<String, List<JsonNode>> groupedFields) { Tab tab = field.getAnnotation(Tab.class); List<JsonNode> fieldsGroupedByTab = groupedFields.get(tab.title()); if (fieldsGroupedByTab == null) { fieldsGroupedByTab = new ArrayList<>(); groupedFields.put(tab.title(), fieldsGroupedByTab); } fieldsGroupedByTab.add(nodes.get(field)); nodes.remove(field); } private void buildFormDefinition(Map<Field, JsonNode> nodes, ObjectMapper mapper, Field field) { Arrays.stream(field.getAnnotations()) .forEach(annotation -> buildFieldDefinition(field, annotation, mapper, nodes)); } private void buildFieldDefinition(Field field, Annotation annotation, ObjectMapper mapper, Map<Field, JsonNode> nodes) { ObjectNode fieldFormDefinition = mapper.createObjectNode(); FormDefinitionGeneratorFactory.getInstance().getGenerator(annotation.annotationType().getName()) .ifPresent(generator -> { generator.generate(fieldFormDefinition, field); nodes.put(field, fieldFormDefinition); }); } private Map<Field, JsonNode> reorderFieldsBasedOnIndex(Map<Field, JsonNode> nodes) { Comparator<? super Entry<Field, JsonNode>> tabIndexComparator = (entry1, entry2) -> { Index field1Index = entry1.getKey().getAnnotation(Index.class); Index field2Index = entry2.getKey().getAnnotation(Index.class); return Integer.compare((field1Index != null ? field1Index.value() : Integer.MAX_VALUE), field2Index != null ? field2Index.value() : Integer.MAX_VALUE); }; return nodes.entrySet().stream().sorted(tabIndexComparator).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new)); } public static UiFormSchemaGenerator get() { if (instance == null) { instance = new UiFormSchemaGenerator(); } return instance; } private UiFormSchemaGenerator() { } }