Parse JsonObject to MerkleTree 07/78807/4
authorFilip Krzywka <filip.krzywka@nokia.com>
Tue, 19 Feb 2019 12:33:45 +0000 (13:33 +0100)
committerFilip Krzywka <filip.krzywka@nokia.com>
Wed, 20 Feb 2019 11:48:44 +0000 (11:48 +0000)
Change-Id: I424eec2c4c47ddff1bff3ef612a7b31a62e1cf3e
Issue-ID: DCAEGEN2-1254
Signed-off-by: Filip Krzywka <filip.krzywka@nokia.com>
rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/listener/MerkleTreeParser.java [new file with mode: 0644]
rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/listener/MerkleTreeParserTest.java [new file with mode: 0644]

diff --git a/rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/listener/MerkleTreeParser.java b/rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/listener/MerkleTreeParser.java
new file mode 100644 (file)
index 0000000..15c4eea
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.listener;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import io.vavr.collection.List;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static java.lang.String.valueOf;
+
+
+/**
+ * Class responsible for creating MerkleTree structure from JsonObject.
+ *
+ * @since 1.1.2
+ */
+class MerkleTreeParser {
+
+    /**
+     * <p> Method used to parse {@link JsonObject} into {@link MerkleTree} structure.</p>
+     * <p> The algorithm will recursively create mapping of (path in tree)->(value) from JsonObject
+     * and use it to create MerkleTree by means of {@link MerkleTree#add(List, Object)} method. </p>
+     * <p> Each JsonObject will append it's key to path until value of primitive type is encountered.
+     * For each JsonArray element artificial path is created by creating lables from sequential integers.
+     * This results in array split into multiple nodes in MerkleTree structure.</p>
+     *
+     * <p>Example. For JsonObject:
+     * <pre>
+     * {
+     *      "p1": "v1",
+     *      "p2": ["v2", "v3"]
+     *      "p3": {
+     *          "p4": "v4"
+     *      }
+     * }
+     * </pre>
+     * following map would be created:</p>
+     * <pre>
+     *  "v1" <- ["p1"]
+     *  "v2" <- ["p2", "0"]
+     *  "v3" <- ["p2", "1"]
+     *  "v4" <- ["p3", "p4"]
+     * </pre>
+     *
+     * @param json JsonObject to be parsed
+     * @since 1.1.2
+     */
+    MerkleTree<String> fromJsonObject(final @NotNull JsonObject json) {
+        MerkleTree<String> tree = MerkleTree.emptyWithDefaultDigest(String::getBytes);
+        for (Entry<String, JsonElement> entry : json.entrySet()) {
+            tree = treeEnhancedWithEntry(tree, entry);
+        }
+
+        return tree;
+    }
+
+    private MerkleTree<String> treeEnhancedWithEntry(final MerkleTree<String> tree,
+                                                     final Entry<String, JsonElement> entry) {
+        return createTreeFromValuesPaths(tree, pathsToValues(entry, List.empty()));
+    }
+
+    private Map<List<String>, String> pathsToValues(Entry<String, JsonElement> entry, List<String> elementPathPrefix) {
+        return pathsToValuesFromJsonElement(entry.getKey(), entry.getValue(), elementPathPrefix);
+    }
+
+    private Map<List<String>, String> pathsToValuesFromJsonElement(final String jsonKey,
+                                                                   final JsonElement element,
+                                                                   final List<String> elementPathPrefix) {
+        final HashMap<List<String>, String> pathToValue = new HashMap<>();
+        final List<String> newPrefix = elementPathPrefix.append(jsonKey);
+
+        if (element.isJsonObject()) {
+            element.getAsJsonObject()
+                    .entrySet()
+                    .forEach(entry -> pathToValue.putAll(pathsToValues(entry, newPrefix)));
+        } else if (element.isJsonArray()) {
+            pathToValue.putAll(handleArray(newPrefix, element.getAsJsonArray()));
+        } else if (element.isJsonPrimitive()) {
+            pathToValue.put(newPrefix, element.getAsString());
+        } else if (element.isJsonNull()) {
+            pathToValue.put(newPrefix, null);
+        }
+        return pathToValue;
+    }
+
+    private HashMap<List<String>, String> handleArray(List<String> newPrefix, JsonArray jsonArray) {
+        final HashMap<List<String>, String> hashMap = new HashMap<>();
+        int labelIndex = 0;
+
+        for (JsonElement jsonElement : jsonArray) {
+            String jsonKey = valueOf(labelIndex++);
+            hashMap.putAll(pathsToValuesFromJsonElement(jsonKey, jsonElement, newPrefix));
+        }
+        return hashMap;
+    }
+
+    private MerkleTree<String> createTreeFromValuesPaths(MerkleTree<String> tree,
+                                                         final Map<List<String>, String> pathToValue) {
+        for (Entry<List<String>, String> entry : pathToValue.entrySet()) {
+            List<String> path = entry.getKey();
+            String value = entry.getValue();
+            tree = tree.add(path, value);
+        }
+        return tree;
+    }
+}
diff --git a/rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/listener/MerkleTreeParserTest.java b/rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/listener/MerkleTreeParserTest.java
new file mode 100644 (file)
index 0000000..080f809
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * ============LICENSE_START====================================
+ * DCAEGEN2-SERVICES-SDK
+ * =========================================================
+ * Copyright (C) 2019 Nokia. All rights reserved.
+ * =========================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=====================================
+ */
+
+package org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.listener;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class MerkleTreeParserTest {
+
+    private final MerkleTreeParser cut = new MerkleTreeParser();
+
+    @Test
+    void fromJsonObject_givenEmptyJsonObject_shouldReturnEmptyMerkleTree() {
+        JsonObject jsonObject = new JsonObject();
+
+        MerkleTree<String> tree = cut.fromJsonObject(jsonObject);
+
+        assertThat(tree.isSame(emptyTree())).isTrue();
+    }
+
+    @Test
+    void fromJsonObject_givenSingleKeyValuePair_shouldReturnSingleNodeTree() {
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("p1", "v1");
+
+        MerkleTree<String> tree = cut.fromJsonObject(jsonObject);
+
+        MerkleTree<String> expected = emptyTree()
+                .add("v1", "p1");
+        assertThat(tree).isEqualTo(expected);
+    }
+
+    @Test
+    void fromJsonObject_givenSingleKeyValuePair_atDeeperPathLevel_shouldReturnTreeWithSingleLeafAndCorrectNodesOnTheWay() {
+        JsonObject singleKeyValuePair = new JsonObject();
+        singleKeyValuePair.addProperty("p3", "v1");
+        JsonObject intermediateNode = new JsonObject();
+        intermediateNode.add("p2", singleKeyValuePair);
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.add("p1", intermediateNode);
+
+        MerkleTree<String> tree = cut.fromJsonObject(jsonObject);
+
+        MerkleTree<String> expected = emptyTree()
+                .add("v1", "p1", "p2", "p3");
+        assertThat(tree).isEqualTo(expected);
+    }
+
+    @Test
+    void fromJsonObject_givenMultipleKeyValuePairs_shouldCreateMultipleLeafs() {
+        JsonObject keyValuePairs = new JsonObject();
+        keyValuePairs.addProperty("A", "vA");
+        keyValuePairs.addProperty("B", "vB");
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.add("p1", keyValuePairs);
+
+        MerkleTree<String> tree = cut.fromJsonObject(jsonObject);
+
+        MerkleTree<String> expected = emptyTree()
+                .add("vA", "p1", "A")
+                .add("vB", "p1", "B");
+        assertThat(tree).isEqualTo(expected);
+    }
+
+    @Test
+    void fromJsonObject_givenJsonArray_shouldCreateMultipleLeafsUnderArtificialNodes() {
+        JsonObject singleKeyValuePair = new JsonObject();
+        singleKeyValuePair.addProperty("p2", "v2");
+        JsonArray jsonArray = new JsonArray();
+        jsonArray.add("v1");
+        jsonArray.add(singleKeyValuePair);
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.add("p1", jsonArray);
+
+        MerkleTree<String> tree = cut.fromJsonObject(jsonObject);
+
+        MerkleTree<String> expected = emptyTree()
+                .add("v1", "p1", "0")
+                .add("v2", "p1", "1", "p2");
+        assertThat(tree).isEqualTo(expected);
+    }
+
+
+    @Test
+    void fromJsonObject_givenMoreComplicatedJson_shouldReturnCorrectTree() {
+        // below example is contained in javadoc for method
+        JsonObject jsonObject2 = new JsonObject();
+        jsonObject2.addProperty("p4", "v4");
+        JsonArray jsonArray = new JsonArray();
+        jsonArray.add("v2");
+        jsonArray.add("v3");
+
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("p1", "v1");
+        jsonObject.add("p2", jsonArray);
+        jsonObject.add("p3", jsonObject2);
+
+        MerkleTree<String> tree = cut.fromJsonObject(jsonObject);
+
+        MerkleTree<String> expected = emptyTree()
+                .add("v1", "p1")
+                .add("v2", "p2", "0")
+                .add("v3", "p2", "1")
+                .add("v4", "p3", "p4");
+        assertThat(tree).isEqualTo(expected);
+    }
+
+    @Test
+    void fromJsonObject_givenNotStringValues_shouldCastAllToString() {
+        JsonArray jsonArray = new JsonArray();
+        jsonArray.add(1);
+        jsonArray.add(2L);
+        jsonArray.add(3.0);
+        jsonArray.add(true);
+        jsonArray.add(new BigInteger("999799799799799"));
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.add("p1", jsonArray);
+
+        MerkleTree<String> tree = cut.fromJsonObject(jsonObject);
+
+        MerkleTree<String> expected = emptyTree()
+                .add("1", "p1", "0")
+                .add("2", "p1", "1")
+                .add("3.0", "p1", "2")
+                .add("true", "p1", "3")
+                .add("999799799799799", "p1", "4");
+        assertThat(tree).isEqualTo(expected);
+    }
+
+    @NotNull
+    private MerkleTree<String> emptyTree() {
+        return MerkleTree.emptyWithDefaultDigest(String::getBytes);
+    }
+}
\ No newline at end of file