package org.gcube.portlets.user.geoportaldataentry.server; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import org.gcube.application.geoportalcommon.shared.geoportal.config.GcubeProfileDV; import org.gcube.portlets.user.geoportaldataentry.shared.GeoNaFormDataObject; import org.gcube.portlets.user.geoportaldataentry.shared.Tree_Node; import org.gcube.portlets.widgets.mpformbuilder.shared.GenericDatasetBean; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.spi.json.JsonOrgJsonProvider; /** * The Class FormDataObjectToJSON. * * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it * * Mar 10, 2022 */ public class FormDataObjectToJSON { public static final String JSON_$_POINTER = "$"; private static final Logger LOG = LoggerFactory.getLogger(FormDataObjectToJSON.class); /** * Convert. * * @param tree_Node the tree node * @param theRootDocument the the root document * @return the JSON object * @throws JSONException the JSON exception */ public JSONObject convert(Tree_Node tree_Node, JSONObject theRootDocument) throws JSONException { if (tree_Node == null) return theRootDocument; // the root, instancing new json document if (tree_Node.isRoot()) { theRootDocument = JSONObjecOrdered.instance(); } Configuration configuration = Configuration.builder().jsonProvider(new JsonOrgJsonProvider()).build(); for (Tree_Node treeNodeChild : tree_Node.getChildren()) { GeoNaFormDataObject gnaFO = treeNodeChild.getData(); // Reading data and profile List listGDB = gnaFO.getListGDB(); GcubeProfileDV profile = gnaFO.getGcubeProfileDV(); LOG.debug("The profile is: " + profile); // Building JSON/section full PATH and section name String sectionJSONPath = ""; String parentPathFromProfile = profile.getParentName() == null ? "" : profile.getParentName(); String theSectionName = profile.getSectionName(); if (theSectionName.compareTo(JSON_$_POINTER) == 0 || theSectionName.compareTo(JSON_$_POINTER + ".") == 0) { sectionJSONPath = JSON_$_POINTER; theSectionName = ""; } else { sectionJSONPath = String.format("%s%s", parentPathFromProfile.endsWith(".") ? parentPathFromProfile : parentPathFromProfile + ".", theSectionName); } LOG.debug("The sectionJSONPath is: " + sectionJSONPath); LOG.info("Current document is: " + theRootDocument); // Building Parent PATH String parentPath = sectionJSONPath.compareTo(JSON_$_POINTER) == 0 ? JSON_$_POINTER : sectionJSONPath.substring(0, sectionJSONPath.lastIndexOf(".")); JsonPath parentJSONPath = JsonPath.compile(parentPath); LOG.info("Putting into parentJSONPath: " + parentJSONPath); List listJSONObject = toListJonObject(listGDB); JSONObject jsonObject = listJSONObject.get(0); // If the maxOccurs is not 1 if (profile.getMaxOccurs() == 0 || profile.getMaxOccurs() > 1) { LOG.debug("maxOccurs is NOT 1"); // Must be an array boolean pathExists = pathExists(theRootDocument, sectionJSONPath + "[*]"); LOG.debug(sectionJSONPath+ "exists? "+pathExists); if (pathExists) { theRootDocument = JsonPath.parse(theRootDocument, configuration).add(sectionJSONPath, jsonObject) .json(); } else { // Adding as array of object JSONArray targetArray = JSONArrayOrdered.instance(); targetArray.put(jsonObject); LOG.debug("JSON array created: " + targetArray); theRootDocument = JsonPath.parse(theRootDocument, configuration) .put(parentJSONPath, theSectionName, targetArray).json(); } LOG.debug("theRootDocument as array is: " + theRootDocument); } else { LOG.debug("maxOccurs is 1"); // Merging as direct properties of the JSON root document if (theSectionName == null || theSectionName.isEmpty()) { deepMerge(jsonObject, theRootDocument); } else { // Putting as child of the JSON document // theRootDocument.put(profile.getSectionName(), jsonObject); theRootDocument = JsonPath.parse(theRootDocument, configuration) .put(parentJSONPath, theSectionName, jsonObject).json(); } LOG.debug("theRootDocument as single object is: " + theRootDocument); } // recursive call... theRootDocument = convert(treeNodeChild, theRootDocument); } LOG.debug("Partial Root Document is: " + theRootDocument); return theRootDocument; } /** * Path exists. * * @param document the document * @param pathExp the path exp * @return true, if successful */ public static boolean pathExists(JSONObject document, String pathExp) { LOG.debug("pathExists called"); try { LOG.debug("pathExists finding: " + pathExp + " into node: " + document); Object object = JsonPath.read(document.toString(), pathExp); if (object != null) { LOG.debug("pathExists returning true"); return true; } } catch (Exception e) { LOG.error("pathExists error", e); return false; } LOG.debug("pathExists returning false"); return false; } /** * Generic dataset bean to JSON. * * @param gdb the gdb * @return the JSON object * @throws JSONException the JSON exception */ protected JSONObject genericDatasetBeanToJSON(GenericDatasetBean gdb) throws JSONException { JSONObject sectJSONObject = JSONObjecOrdered.instance(); LinkedHashMap> mapFields = gdb.getFormDataEntryFields(); LOG.debug("Map ordered: " + mapFields); for (String key : mapFields.keySet()) { List listValues = mapFields.get(key); if (listValues == null || listValues.isEmpty()) { continue; } // key/value as string if (listValues.size() == 1) { sectJSONObject.put(key, listValues.get(0)); continue; } // value is a list JSONArray array = new JSONArray(); for (String value : listValues) { array.put(value); } sectJSONObject.put(key, array); } return sectJSONObject; } /** * To list jon object. * * @param listGDB the list GDB * @return the list * @throws JSONException the JSON exception */ private List toListJonObject(List listGDB) throws JSONException { List listJSONObject = new ArrayList(); for (GenericDatasetBean gdb : listGDB) { JSONObject jsonObject = genericDatasetBeanToJSON(gdb); listJSONObject.add(jsonObject); } LOG.info("returning : " + listJSONObject); return listJSONObject; } /** * Merge "source" into "target". If fields have equal name, merge them * recursively. * * @param source the source * @param target the target * @return the merged object (target). * @throws JSONException the JSON exception */ public static JSONObject deepMerge(JSONObject source, JSONObject target) throws JSONException { for (String key : JSONObject.getNames(source)) { Object value = source.get(key); if (!target.has(key)) { // new value for "key": target.put(key, value); } else { // existing value for "key" - recursively deep merge: if (value instanceof JSONObject) { JSONObject valueJson = (JSONObject) value; deepMerge(valueJson, target.getJSONObject(key)); } else { target.put(key, value); } } } return target; } /** * The Class JSONObjecOrdered. * * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it * * Mar 10, 2022 */ public static class JSONObjecOrdered { /** * Instance. * * @return the JSON object */ public static JSONObject instance() { JSONObject jsonObject = new JSONObject(); try { Field changeMap = jsonObject.getClass().getDeclaredField("map"); changeMap.setAccessible(true); changeMap.set(jsonObject, new LinkedHashMap<>()); changeMap.setAccessible(false); } catch (IllegalAccessException | NoSuchFieldException e) { } return jsonObject; } } /** * The Class JSONObjecOrdered. * * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it * * Mar 10, 2022 */ public static class JSONArrayOrdered { /** * Instance. * * @return the JSON object */ public static JSONArray instance() { JSONArray jsonArray = new JSONArray(); try { Field changeMap = jsonArray.getClass().getDeclaredField("map"); changeMap.setAccessible(true); changeMap.set(jsonArray, new LinkedHashMap<>()); changeMap.setAccessible(false); } catch (IllegalAccessException | NoSuchFieldException e) { } return jsonArray; } } }