From fc88ac806f5650ec9f70c4849e3815132038ec5c Mon Sep 17 00:00:00 2001 From: Filip Krzywka Date: Tue, 19 Feb 2019 13:33:45 +0100 Subject: Parse JsonObject to MerkleTree Change-Id: I424eec2c4c47ddff1bff3ef612a7b31a62e1cf3e Issue-ID: DCAEGEN2-1254 Signed-off-by: Filip Krzywka --- .../cbs/client/api/listener/MerkleTreeParser.java | 129 +++++++++++++++++ .../client/api/listener/MerkleTreeParserTest.java | 159 +++++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/listener/MerkleTreeParser.java create mode 100644 rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/listener/MerkleTreeParserTest.java (limited to 'rest-services/cbs-client/src') 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 index 00000000..15c4eea2 --- /dev/null +++ b/rest-services/cbs-client/src/main/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/listener/MerkleTreeParser.java @@ -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 { + + /** + *

Method used to parse {@link JsonObject} into {@link MerkleTree} structure.

+ *

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.

+ *

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.

+ * + *

Example. For JsonObject: + *

+     * {
+     *      "p1": "v1",
+     *      "p2": ["v2", "v3"]
+     *      "p3": {
+     *          "p4": "v4"
+     *      }
+     * }
+     * 
+ * following map would be created:

+ *
+     *  "v1" <- ["p1"]
+     *  "v2" <- ["p2", "0"]
+     *  "v3" <- ["p2", "1"]
+     *  "v4" <- ["p3", "p4"]
+     * 
+ * + * @param json JsonObject to be parsed + * @since 1.1.2 + */ + MerkleTree fromJsonObject(final @NotNull JsonObject json) { + MerkleTree tree = MerkleTree.emptyWithDefaultDigest(String::getBytes); + for (Entry entry : json.entrySet()) { + tree = treeEnhancedWithEntry(tree, entry); + } + + return tree; + } + + private MerkleTree treeEnhancedWithEntry(final MerkleTree tree, + final Entry entry) { + return createTreeFromValuesPaths(tree, pathsToValues(entry, List.empty())); + } + + private Map, String> pathsToValues(Entry entry, List elementPathPrefix) { + return pathsToValuesFromJsonElement(entry.getKey(), entry.getValue(), elementPathPrefix); + } + + private Map, String> pathsToValuesFromJsonElement(final String jsonKey, + final JsonElement element, + final List elementPathPrefix) { + final HashMap, String> pathToValue = new HashMap<>(); + final List 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, String> handleArray(List newPrefix, JsonArray jsonArray) { + final HashMap, 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 createTreeFromValuesPaths(MerkleTree tree, + final Map, String> pathToValue) { + for (Entry, String> entry : pathToValue.entrySet()) { + List 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 index 00000000..080f8097 --- /dev/null +++ b/rest-services/cbs-client/src/test/java/org/onap/dcaegen2/services/sdk/rest/services/cbs/client/api/listener/MerkleTreeParserTest.java @@ -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 tree = cut.fromJsonObject(jsonObject); + + assertThat(tree.isSame(emptyTree())).isTrue(); + } + + @Test + void fromJsonObject_givenSingleKeyValuePair_shouldReturnSingleNodeTree() { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("p1", "v1"); + + MerkleTree tree = cut.fromJsonObject(jsonObject); + + MerkleTree 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 tree = cut.fromJsonObject(jsonObject); + + MerkleTree 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 tree = cut.fromJsonObject(jsonObject); + + MerkleTree 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 tree = cut.fromJsonObject(jsonObject); + + MerkleTree 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 tree = cut.fromJsonObject(jsonObject); + + MerkleTree 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 tree = cut.fromJsonObject(jsonObject); + + MerkleTree 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 emptyTree() { + return MerkleTree.emptyWithDefaultDigest(String::getBytes); + } +} \ No newline at end of file -- cgit 1.2.3-korg