summaryrefslogtreecommitdiffstats
path: root/cps-service/src
diff options
context:
space:
mode:
Diffstat (limited to 'cps-service/src')
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsDataService.java38
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java91
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java23
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java39
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/model/ModuleReference.java1
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/ContentType.java26
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java185
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/YangUtils.java246
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy60
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy9
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy83
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy2
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/JsonParserStreamSpec.groovy27
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy61
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy73
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy4
-rw-r--r--cps-service/src/test/resources/bookstore.json8
-rw-r--r--cps-service/src/test/resources/bookstore.xml19
-rw-r--r--cps-service/src/test/resources/bookstore_xpath.xml17
-rw-r--r--cps-service/src/test/resources/test-tree.xml27
20 files changed, 840 insertions, 199 deletions
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
index b2e8c5ba42..012d7f8259 100644
--- a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
@@ -3,6 +3,7 @@
* Copyright (C) 2020-2022 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada
+ * Modifications Copyright (C) 2022 Deutsche Telekom AG
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +28,7 @@ import java.util.Collection;
import java.util.Map;
import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.utils.ContentType;
/*
* Datastore interface for handling CPS data.
@@ -38,10 +40,22 @@ public interface CpsDataService {
*
* @param dataspaceName dataspace name
* @param anchorName anchor name
- * @param jsonData json data
+ * @param nodeData node data
* @param observedTimestamp observedTimestamp
*/
- void saveData(String dataspaceName, String anchorName, String jsonData, OffsetDateTime observedTimestamp);
+ void saveData(String dataspaceName, String anchorName, String nodeData, OffsetDateTime observedTimestamp);
+
+ /**
+ * Persists data for the given anchor and dataspace.
+ *
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param nodeData node data
+ * @param observedTimestamp observedTimestamp
+ * @param contentType node data content type
+ */
+ void saveData(String dataspaceName, String anchorName, String nodeData, OffsetDateTime observedTimestamp,
+ ContentType contentType);
/**
* Persists child data fragment under existing data node for the given anchor and dataspace.
@@ -49,11 +63,25 @@ public interface CpsDataService {
* @param dataspaceName dataspace name
* @param anchorName anchor name
* @param parentNodeXpath parent node xpath
- * @param jsonData json data
+ * @param nodeData node data
* @param observedTimestamp observedTimestamp
*/
- void saveData(String dataspaceName, String anchorName, String parentNodeXpath, String jsonData,
- OffsetDateTime observedTimestamp);
+ void saveData(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData,
+ OffsetDateTime observedTimestamp);
+
+ /**
+ * Persists child data fragment under existing data node for the given anchor, dataspace and content type.
+ *
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param parentNodeXpath parent node xpath
+ * @param nodeData node data
+ * @param observedTimestamp observedTimestamp
+ * @param contentType node data content type
+ *
+ */
+ void saveData(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData,
+ OffsetDateTime observedTimestamp, ContentType contentType);
/**
* Persists child data fragment representing one or more list elements under existing data node for the
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
index b08d8c1eba..65dfa7f5c6 100755
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
@@ -3,6 +3,8 @@
* Copyright (C) 2021-2022 Nordix Foundation
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
+ * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022 Deutsche Telekom AG
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +29,7 @@ import static org.onap.cps.notification.Operation.DELETE;
import static org.onap.cps.notification.Operation.UPDATE;
import java.time.OffsetDateTime;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -44,8 +47,9 @@ import org.onap.cps.spi.model.Anchor;
import org.onap.cps.spi.model.DataNode;
import org.onap.cps.spi.model.DataNodeBuilder;
import org.onap.cps.spi.utils.CpsValidator;
+import org.onap.cps.utils.ContentType;
import org.onap.cps.utils.YangUtils;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.springframework.stereotype.Service;
@@ -64,20 +68,35 @@ public class CpsDataServiceImpl implements CpsDataService {
private final CpsValidator cpsValidator;
@Override
- public void saveData(final String dataspaceName, final String anchorName, final String jsonData,
+ public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
final OffsetDateTime observedTimestamp) {
+ saveData(dataspaceName, anchorName, nodeData, observedTimestamp, ContentType.JSON);
+ }
+
+ @Override
+ public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
+ final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- final DataNode dataNode = buildDataNode(dataspaceName, anchorName, ROOT_NODE_XPATH, jsonData);
- cpsDataPersistenceService.storeDataNode(dataspaceName, anchorName, dataNode);
+ final Collection<DataNode> dataNodes =
+ buildDataNodes(dataspaceName, anchorName, ROOT_NODE_XPATH, nodeData, contentType);
+ cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes);
processDataUpdatedEventAsync(dataspaceName, anchorName, ROOT_NODE_XPATH, CREATE, observedTimestamp);
}
@Override
public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
- final String jsonData, final OffsetDateTime observedTimestamp) {
+ final String nodeData, final OffsetDateTime observedTimestamp) {
+ saveData(dataspaceName, anchorName, parentNodeXpath, nodeData, observedTimestamp, ContentType.JSON);
+ }
+
+ @Override
+ public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final String nodeData, final OffsetDateTime observedTimestamp,
+ final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- final DataNode dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
- cpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, parentNodeXpath, dataNode);
+ final Collection<DataNode> dataNodes =
+ buildDataNodes(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType);
+ cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, CREATE, observedTimestamp);
}
@@ -86,7 +105,7 @@ public class CpsDataServiceImpl implements CpsDataService {
final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Collection<DataNode> listElementDataNodeCollection =
- buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData);
+ buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON);
cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
listElementDataNodeCollection);
processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
@@ -97,7 +116,7 @@ public class CpsDataServiceImpl implements CpsDataService {
final Collection<String> jsonDataList, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Collection<Collection<DataNode>> listElementDataNodeCollections =
- buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonDataList);
+ buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonDataList, ContentType.JSON);
cpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, parentNodeXpath,
listElementDataNodeCollections);
processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
@@ -114,7 +133,7 @@ public class CpsDataServiceImpl implements CpsDataService {
public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String jsonData, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- final DataNode dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
+ final DataNode dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON);
cpsDataPersistenceService
.updateDataLeaves(dataspaceName, anchorName, dataNode.getXpath(), dataNode.getLeaves());
processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
@@ -128,7 +147,7 @@ public class CpsDataServiceImpl implements CpsDataService {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Collection<DataNode> dataNodeUpdates =
buildDataNodes(dataspaceName, anchorName,
- parentNodeXpath, dataNodeUpdatesAsJson);
+ parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
for (final DataNode dataNodeUpdate : dataNodeUpdates) {
processDataNodeUpdate(dataspaceName, anchorName, dataNodeUpdate);
}
@@ -161,8 +180,10 @@ public class CpsDataServiceImpl implements CpsDataService {
final String parentNodeXpath, final String jsonData,
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- final DataNode dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
- cpsDataPersistenceService.updateDataNodeAndDescendants(dataspaceName, anchorName, dataNode);
+ final Collection<DataNode> dataNodes =
+ buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON);
+ final ArrayList<DataNode> nodes = new ArrayList<>(dataNodes);
+ cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, nodes);
processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
}
@@ -183,7 +204,7 @@ public class CpsDataServiceImpl implements CpsDataService {
final String jsonData, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Collection<DataNode> newListElements =
- buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData);
+ buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData, ContentType.JSON);
replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
}
@@ -220,42 +241,54 @@ public class CpsDataServiceImpl implements CpsDataService {
}
private DataNode buildDataNode(final String dataspaceName, final String anchorName,
- final String parentNodeXpath, final String jsonData) {
+ final String parentNodeXpath, final String nodeData,
+ final ContentType contentType) {
final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
final SchemaContext schemaContext = getSchemaContext(dataspaceName, anchor.getSchemaSetName());
if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
- final NormalizedNode normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext);
- return new DataNodeBuilder().withNormalizedNodeTree(normalizedNode).build();
+ final ContainerNode containerNode = YangUtils.parseData(contentType, nodeData, schemaContext);
+ return new DataNodeBuilder().withContainerNode(containerNode).build();
}
- final NormalizedNode normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath);
+ final ContainerNode containerNode = YangUtils.parseData(contentType, nodeData, schemaContext, parentNodeXpath);
+
return new DataNodeBuilder()
- .withParentNodeXpath(parentNodeXpath)
- .withNormalizedNodeTree(normalizedNode)
- .build();
+ .withParentNodeXpath(parentNodeXpath)
+ .withContainerNode(containerNode)
+ .build();
}
private List<DataNode> buildDataNodes(final String dataspaceName, final String anchorName,
final Map<String, String> nodesJsonData) {
return nodesJsonData.entrySet().stream().map(nodeJsonData ->
buildDataNode(dataspaceName, anchorName, nodeJsonData.getKey(),
- nodeJsonData.getValue())).collect(Collectors.toList());
+ nodeJsonData.getValue(), ContentType.JSON)).collect(Collectors.toList());
}
private Collection<DataNode> buildDataNodes(final String dataspaceName,
final String anchorName,
final String parentNodeXpath,
- final String jsonData) {
+ final String nodeData,
+ final ContentType contentType) {
final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
final SchemaContext schemaContext = getSchemaContext(dataspaceName, anchor.getSchemaSetName());
-
- final NormalizedNode normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath);
+ if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
+ final ContainerNode containerNode = YangUtils.parseData(contentType, nodeData, schemaContext);
+ final Collection<DataNode> dataNodes = new DataNodeBuilder()
+ .withContainerNode(containerNode)
+ .buildCollection();
+ if (dataNodes.isEmpty()) {
+ throw new DataValidationException("Invalid data.", "No data nodes provided");
+ }
+ return dataNodes;
+ }
+ final ContainerNode containerNode = YangUtils.parseData(contentType, nodeData, schemaContext, parentNodeXpath);
final Collection<DataNode> dataNodes = new DataNodeBuilder()
.withParentNodeXpath(parentNodeXpath)
- .withNormalizedNodeTree(normalizedNode)
+ .withContainerNode(containerNode)
.buildCollection();
if (dataNodes.isEmpty()) {
throw new DataValidationException("Invalid data.", "No data nodes provided");
@@ -265,9 +298,9 @@ public class CpsDataServiceImpl implements CpsDataService {
}
private Collection<Collection<DataNode>> buildDataNodes(final String dataspaceName, final String anchorName,
- final String parentNodeXpath, final Collection<String> jsonDataList) {
- return jsonDataList.stream()
- .map(jsonData -> buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData))
+ final String parentNodeXpath, final Collection<String> nodeDataList, final ContentType contentType) {
+ return nodeDataList.stream()
+ .map(nodeData -> buildDataNodes(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType))
.collect(Collectors.toList());
}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
index 28b18b3b5c..b9da4af025 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
@@ -3,6 +3,7 @@
* Copyright (C) 2020-2022 Nordix Foundation.
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022 Bell Canada
+ * Modifications Copyright (C) 2022 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,16 +36,27 @@ import org.onap.cps.spi.model.DataNode;
*/
public interface CpsDataPersistenceService {
+
/**
* Store a datanode.
*
* @param dataspaceName dataspace name
* @param anchorName anchor name
* @param dataNode data node
+ * @deprecated Please use {@link #storeDataNodes(String, String, Collection)} as it supports multiple data nodes.
*/
+ @Deprecated
void storeDataNode(String dataspaceName, String anchorName, DataNode dataNode);
/**
+ * Store multiple datanodes at once.
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param dataNodes data nodes
+ */
+ void storeDataNodes(String dataspaceName, String anchorName, Collection<DataNode> dataNodes);
+
+ /**
* Add a child to a Fragment.
*
* @param dataspaceName dataspace name
@@ -55,6 +67,16 @@ public interface CpsDataPersistenceService {
void addChildDataNode(String dataspaceName, String anchorName, String parentXpath, DataNode dataNode);
/**
+ * Add multiple children to a Fragment.
+ *
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param parentXpath parent xpath
+ * @param dataNodes collection of dataNodes
+ */
+ void addChildDataNodes(String dataspaceName, String anchorName, String parentXpath, Collection<DataNode> dataNodes);
+
+ /**
* Adds list child elements to a Fragment.
*
* @param dataspaceName dataspace name
@@ -62,7 +84,6 @@ public interface CpsDataPersistenceService {
* @param parentNodeXpath parent node xpath
* @param listElementsCollection collection of data nodes representing list elements
*/
-
void addListElements(String dataspaceName, String anchorName, String parentNodeXpath,
Collection<DataNode> listElementsCollection);
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
index 1d8bac0dde..b23cdfc8d1 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java
@@ -3,6 +3,7 @@
* Copyright (C) 2021 Bell Canada. All rights reserved.
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022 Nordix Foundation.
+ * Modifications Copyright (C) 2022 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +36,7 @@ import org.onap.cps.spi.exceptions.DataValidationException;
import org.onap.cps.utils.YangUtils;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
@@ -46,7 +48,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.ValueNode;
@Slf4j
public class DataNodeBuilder {
- private NormalizedNode normalizedNodeTree;
+ private ContainerNode containerNode;
private String xpath;
private String moduleNamePrefix;
private String parentNodeXpath = "";
@@ -64,15 +66,14 @@ public class DataNodeBuilder {
return this;
}
-
/**
- * To use {@link NormalizedNode} for creating {@link DataNode}.
+ * To use {@link Collection} of Normalized Nodes for creating {@link DataNode}.
*
- * @param normalizedNodeTree used for creating the Data Node
+ * @param containerNode used for creating the Data Node
* @return this {@link DataNodeBuilder} object
*/
- public DataNodeBuilder withNormalizedNodeTree(final NormalizedNode normalizedNodeTree) {
- this.normalizedNodeTree = normalizedNodeTree;
+ public DataNodeBuilder withContainerNode(final ContainerNode containerNode) {
+ this.containerNode = containerNode;
return this;
}
@@ -128,11 +129,10 @@ public class DataNodeBuilder {
* @return {@link DataNode}
*/
public DataNode build() {
- if (normalizedNodeTree != null) {
- return buildFromNormalizedNodeTree();
- } else {
- return buildFromAttributes();
+ if (containerNode != null) {
+ return buildFromContainerNode();
}
+ return buildFromAttributes();
}
/**
@@ -141,11 +141,10 @@ public class DataNodeBuilder {
* @return {@link DataNode} {@link Collection}
*/
public Collection<DataNode> buildCollection() {
- if (normalizedNodeTree != null) {
- return buildCollectionFromNormalizedNodeTree();
- } else {
- return Set.of(buildFromAttributes());
+ if (containerNode != null) {
+ return buildCollectionFromContainerNode();
}
+ return Collections.emptySet();
}
private DataNode buildFromAttributes() {
@@ -157,8 +156,8 @@ public class DataNodeBuilder {
return dataNode;
}
- private DataNode buildFromNormalizedNodeTree() {
- final Collection<DataNode> dataNodeCollection = buildCollectionFromNormalizedNodeTree();
+ private DataNode buildFromContainerNode() {
+ final Collection<DataNode> dataNodeCollection = buildCollectionFromContainerNode();
if (!dataNodeCollection.iterator().hasNext()) {
throw new DataValidationException(
"Unsupported xpath: ", "Unsupported xpath as it is referring to one element");
@@ -166,9 +165,13 @@ public class DataNodeBuilder {
return dataNodeCollection.iterator().next();
}
- private Collection<DataNode> buildCollectionFromNormalizedNodeTree() {
+ private Collection<DataNode> buildCollectionFromContainerNode() {
final var parentDataNode = new DataNodeBuilder().withXpath(parentNodeXpath).build();
- addDataNodeFromNormalizedNode(parentDataNode, normalizedNodeTree);
+ if (containerNode.body() != null) {
+ for (final NormalizedNode normalizedNode: containerNode.body()) {
+ addDataNodeFromNormalizedNode(parentDataNode, normalizedNode);
+ }
+ }
return parentDataNode.getChildDataNodes();
}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/ModuleReference.java b/cps-service/src/main/java/org/onap/cps/spi/model/ModuleReference.java
index 569f0a06ed..18d55d5230 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/model/ModuleReference.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/ModuleReference.java
@@ -49,4 +49,5 @@ public class ModuleReference implements Serializable {
this.revision = revision;
this.namespace = "";
}
+
}
diff --git a/cps-service/src/main/java/org/onap/cps/utils/ContentType.java b/cps-service/src/main/java/org/onap/cps/utils/ContentType.java
new file mode 100644
index 0000000000..f888504843
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/utils/ContentType.java
@@ -0,0 +1,26 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Deutsche Telekom AG
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.utils;
+
+public enum ContentType {
+ JSON,
+ XML
+}
diff --git a/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java
new file mode 100644
index 0000000000..09f2e16c6a
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/utils/XmlFileUtils.java
@@ -0,0 +1,185 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Deutsche Telekom AG
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.onap.cps.spi.exceptions.DataValidationException;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class XmlFileUtils {
+
+ private static final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ private static boolean isNewDocumentBuilderFactoryInstance = true;
+ private static final TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ private static boolean isNewTransformerFactoryInstance = true;
+ private static final Pattern XPATH_PROPERTY_REGEX =
+ Pattern.compile("\\[@(\\S{1,100})=['\\\"](\\S{1,100})['\\\"]\\]");
+
+ /**
+ * Prepare XML content.
+ *
+ * @param xmlContent XML content sent to store
+ * @param schemaContext schema context
+ *
+ * @return XML content wrapped by root node (if needed)
+ */
+ public static String prepareXmlContent(final String xmlContent, final SchemaContext schemaContext)
+ throws IOException, ParserConfigurationException, TransformerException, SAXException {
+ return addRootNodeToXmlContent(xmlContent, schemaContext.getModules().iterator().next().getName(),
+ YangUtils.DATA_ROOT_NODE_NAMESPACE);
+ }
+
+ /**
+ * Prepare XML content.
+ *
+ * @param xmlContent XML content sent to store
+ * @param parentSchemaNode Parent schema node
+ * @param xpath Parent xpath
+ *
+ * @return XML content wrapped by root node (if needed)
+ */
+ public static String prepareXmlContent(final String xmlContent,
+ final DataSchemaNode parentSchemaNode,
+ final String xpath)
+ throws IOException, ParserConfigurationException, TransformerException, SAXException {
+ final String namespace = parentSchemaNode.getQName().getNamespace().toString();
+ final String parentXpathPart = xpath.substring(xpath.lastIndexOf('/') + 1);
+ final Matcher regexMatcher = XPATH_PROPERTY_REGEX.matcher(parentXpathPart);
+ if (regexMatcher.find()) {
+ final HashMap<String, String> rootNodePropertyMap = new HashMap<>();
+ rootNodePropertyMap.put(regexMatcher.group(1), regexMatcher.group(2));
+ return addRootNodeToXmlContent(xmlContent, parentSchemaNode.getQName().getLocalName(), namespace,
+ rootNodePropertyMap);
+ }
+
+ return addRootNodeToXmlContent(xmlContent, parentSchemaNode.getQName().getLocalName(), namespace);
+ }
+
+ private static String addRootNodeToXmlContent(final String xmlContent,
+ final String rootNodeTagName,
+ final String namespace,
+ final Map<String, String> rootNodeProperty)
+ throws IOException, SAXException, ParserConfigurationException, TransformerException {
+ final DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
+ final StringBuilder xmlStringBuilder = new StringBuilder();
+ xmlStringBuilder.append(xmlContent);
+ final Document document = documentBuilder.parse(
+ new ByteArrayInputStream(xmlStringBuilder.toString().getBytes(StandardCharsets.UTF_8)));
+ final Element root = document.getDocumentElement();
+ if (!root.getTagName().equals(rootNodeTagName)
+ && !root.getTagName().equals(YangUtils.DATA_ROOT_NODE_TAG_NAME)) {
+ final Document documentWithRootNode = addDataRootNode(root, rootNodeTagName, namespace, rootNodeProperty);
+ documentWithRootNode.setXmlStandalone(true);
+ final Transformer transformer = getTransformerFactory().newTransformer();
+ final StringWriter stringWriter = new StringWriter();
+ transformer.transform(new DOMSource(documentWithRootNode), new StreamResult(stringWriter));
+ return stringWriter.toString();
+ }
+ return xmlContent;
+ }
+
+ /**
+ * Add root node to XML content.
+ *
+ * @param xmlContent XML content to add root node into
+ * @param rootNodeTagName Root node tag name
+ * @return XML content with root node tag added (if needed)
+ */
+ public static String addRootNodeToXmlContent(final String xmlContent,
+ final String rootNodeTagName,
+ final String namespace)
+ throws IOException, ParserConfigurationException, TransformerException, SAXException {
+ return addRootNodeToXmlContent(xmlContent, rootNodeTagName, namespace, new HashMap<>());
+ }
+
+ /**
+ * Add root node into DOM element.
+ *
+ * @param node DOM element to add root node into
+ * @param tagName Root tag name to add
+ * @return DOM element with a root node
+ */
+ static Document addDataRootNode(final Element node,
+ final String tagName,
+ final String namespace,
+ final Map<String, String> rootNodeProperty) {
+ try {
+ final DocumentBuilder documentBuilder = getDocumentBuilderFactory().newDocumentBuilder();
+ final Document document = documentBuilder.newDocument();
+ final Element rootElement = document.createElementNS(namespace, tagName);
+ for (final Map.Entry<String, String> entry : rootNodeProperty.entrySet()) {
+ final Element propertyElement = document.createElement(entry.getKey());
+ propertyElement.setTextContent(entry.getValue());
+ rootElement.appendChild(propertyElement);
+ }
+ rootElement.appendChild(document.adoptNode(node));
+ document.appendChild(rootElement);
+ return document;
+ } catch (final ParserConfigurationException exception) {
+ throw new DataValidationException("Can't parse XML", "XML can't be parsed", exception);
+ }
+ }
+
+ private static DocumentBuilderFactory getDocumentBuilderFactory() {
+
+ if (isNewDocumentBuilderFactoryInstance) {
+ documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+ documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
+ isNewDocumentBuilderFactoryInstance = false;
+ }
+
+ return documentBuilderFactory;
+ }
+
+ private static TransformerFactory getTransformerFactory() {
+
+ if (isNewTransformerFactoryInstance) {
+ transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+ transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
+ isNewTransformerFactoryInstance = false;
+ }
+
+ return transformerFactory;
+ }
+}
diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
index 48241ed392..c0dfe5205a 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
@@ -3,6 +3,8 @@
* Copyright (C) 2020-2022 Nordix Foundation
* Modifications Copyright (C) 2021 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
+ * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022 Deutsche Telekom AG
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,24 +28,40 @@ import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.JsonReader;
import java.io.IOException;
import java.io.StringReader;
+import java.net.URISyntaxException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.transform.TransformerException;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.cpspath.parser.CpsPathUtil;
+import org.onap.cps.cpspath.parser.PathParsingException;
import org.onap.cps.spi.exceptions.DataValidationException;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
+import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
@@ -53,101 +71,191 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.xml.sax.SAXException;
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class YangUtils {
- private static final String XPATH_DELIMITER_REGEX = "\\/";
- private static final String XPATH_NODE_KEY_ATTRIBUTES_REGEX = "\\[.*?\\]";
+ public static final String DATA_ROOT_NODE_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0";
+ public static final String DATA_ROOT_NODE_TAG_NAME = "data";
/**
- * Parses jsonData into NormalizedNode according to given schema context.
+ * Parses data into Collection of NormalizedNode according to given schema context.
*
- * @param jsonData json data as string
+ * @param nodeData data string
+ * @param schemaContext schema context describing associated data model
+ * @return the NormalizedNode object
+ */
+ public static ContainerNode parseData(final ContentType contentType,
+ final String nodeData,
+ final SchemaContext schemaContext) {
+ if (contentType == ContentType.JSON) {
+ return parseJsonDataWithOptionalParent(nodeData, schemaContext, Optional.empty());
+ }
+ return parseXmlDataWithOptionalParent(nodeData, schemaContext, Optional.empty());
+ }
+
+ /**
+ * Parses data into NormalizedNode according to given schema context.
+ *
+ * @param nodeData data string
* @param schemaContext schema context describing associated data model
* @return the NormalizedNode object
*/
- public static NormalizedNode parseJsonData(final String jsonData, final SchemaContext schemaContext) {
- return parseJsonData(jsonData, schemaContext, Optional.empty());
+ public static ContainerNode parseData(final ContentType contentType,
+ final String nodeData,
+ final SchemaContext schemaContext,
+ final String parentNodeXpath) {
+ if (contentType == ContentType.JSON) {
+ return parseJsonDataWithOptionalParent(nodeData, schemaContext, Optional.of(parentNodeXpath));
+ }
+ return parseXmlDataWithOptionalParent(nodeData, schemaContext, Optional.of(parentNodeXpath));
}
/**
- * Parses jsonData into NormalizedNode according to given schema context.
+ * Parses data into Collection of NormalizedNode according to given schema context.
+ *
+ * @param jsonData json data as string
+ * @param schemaContext schema context describing associated data model
+ * @return the Collection of NormalizedNode object
+ */
+ public static ContainerNode parseJsonData(final String jsonData, final SchemaContext schemaContext) {
+ return parseJsonDataWithOptionalParent(jsonData, schemaContext, Optional.empty());
+ }
+
+ /**
+ * Parses jsonData into Collection of NormalizedNode according to given schema context.
*
* @param jsonData json data fragment as string
* @param schemaContext schema context describing associated data model
* @param parentNodeXpath the xpath referencing the parent node current data fragment belong to
* @return the NormalizedNode object
*/
- public static NormalizedNode parseJsonData(final String jsonData, final SchemaContext schemaContext,
- final String parentNodeXpath) {
- final Collection<QName> dataSchemaNodeIdentifiers =
- getDataSchemaNodeIdentifiersByXpath(parentNodeXpath, schemaContext);
- return parseJsonData(jsonData, schemaContext, Optional.of(dataSchemaNodeIdentifiers));
+ public static ContainerNode parseJsonData(final String jsonData,
+ final SchemaContext schemaContext,
+ final String parentNodeXpath) {
+ return parseJsonDataWithOptionalParent(jsonData, schemaContext, Optional.of(parentNodeXpath));
+ }
+
+ /**
+ * Create an xpath form a Yang Tools NodeIdentifier (i.e. PathArgument).
+ *
+ * @param nodeIdentifier the NodeIdentifier
+ * @return a xpath
+ */
+ public static String buildXpath(final YangInstanceIdentifier.PathArgument nodeIdentifier) {
+ final StringBuilder xpathBuilder = new StringBuilder();
+ xpathBuilder.append("/").append(nodeIdentifier.getNodeType().getLocalName());
+
+ if (nodeIdentifier instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
+ xpathBuilder.append(getKeyAttributesStatement(
+ (YangInstanceIdentifier.NodeIdentifierWithPredicates) nodeIdentifier));
+ }
+ return xpathBuilder.toString();
}
- private static NormalizedNode parseJsonData(final String jsonData, final SchemaContext schemaContext,
- final Optional<Collection<QName>> dataSchemaNodeIdentifiers) {
+ private static ContainerNode parseJsonDataWithOptionalParent(final String jsonData,
+ final SchemaContext schemaContext,
+ final Optional<String> parentNodeXpath) {
final JSONCodecFactory jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02
.getShared((EffectiveModelContext) schemaContext);
- final NormalizedNodeResult normalizedNodeResult = new NormalizedNodeResult();
+ final DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> dataContainerNodeBuilder =
+ Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(
+ QName.create(DATA_ROOT_NODE_NAMESPACE, DATA_ROOT_NODE_TAG_NAME)
+ ));
final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter
- .from(normalizedNodeResult);
+ .from(dataContainerNodeBuilder);
final JsonReader jsonReader = new JsonReader(new StringReader(jsonData));
final JsonParserStream jsonParserStream;
- if (dataSchemaNodeIdentifiers.isPresent()) {
+ if (parentNodeXpath.isPresent()) {
+ final Collection<QName> dataSchemaNodeIdentifiers
+ = getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath.get());
final EffectiveModelContext effectiveModelContext = ((EffectiveModelContext) schemaContext);
final EffectiveStatementInference effectiveStatementInference =
SchemaInferenceStack.of(effectiveModelContext,
- SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers.get())).toInference();
+ SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference();
jsonParserStream =
JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory, effectiveStatementInference);
} else {
jsonParserStream = JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory);
}
- try {
+ try (jsonParserStream) {
jsonParserStream.parse(jsonReader);
- jsonParserStream.close();
- } catch (final JsonSyntaxException exception) {
+ } catch (final IOException | JsonSyntaxException exception) {
throw new DataValidationException(
- "Failed to parse json data: " + jsonData, exception.getMessage(), exception);
- } catch (final IOException | IllegalStateException illegalStateException) {
+ "Failed to parse json data: " + jsonData, exception.getMessage(), exception);
+ } catch (final IllegalStateException | IllegalArgumentException exception) {
throw new DataValidationException(
- "Failed to parse json data. Unsupported xpath or json data:" + jsonData, illegalStateException
- .getMessage(), illegalStateException);
+ "Failed to parse json data. Unsupported xpath or json data:" + jsonData, exception
+ .getMessage(), exception);
}
- return normalizedNodeResult.getResult();
+ return dataContainerNodeBuilder.build();
}
- /**
- * Create an xpath form a Yang Tools NodeIdentifier (i.e. PathArgument).
- *
- * @param nodeIdentifier the NodeIdentifier
- * @return an xpath
- */
- public static String buildXpath(final YangInstanceIdentifier.PathArgument nodeIdentifier) {
- final StringBuilder xpathBuilder = new StringBuilder();
- xpathBuilder.append("/").append(nodeIdentifier.getNodeType().getLocalName());
+ private static ContainerNode parseXmlDataWithOptionalParent(final String xmlData,
+ final SchemaContext schemaContext,
+ final Optional<String> parentNodeXpath) {
+ final XMLInputFactory factory = XMLInputFactory.newInstance();
+ factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
+ final NormalizedNodeResult normalizedNodeResult = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter
+ .from(normalizedNodeResult);
- if (nodeIdentifier instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
- xpathBuilder.append(getKeyAttributesStatement(
- (YangInstanceIdentifier.NodeIdentifierWithPredicates) nodeIdentifier));
+ final EffectiveModelContext effectiveModelContext = (EffectiveModelContext) schemaContext;
+ final XmlParserStream xmlParserStream;
+ final String preparedXmlContent;
+ try {
+ if (parentNodeXpath.isPresent()) {
+ final DataSchemaNode parentSchemaNode =
+ (DataSchemaNode) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath.get(), schemaContext)
+ .get("dataSchemaNode");
+ final Collection<QName> dataSchemaNodeIdentifiers =
+ getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath.get());
+ final EffectiveStatementInference effectiveStatementInference =
+ SchemaInferenceStack.of(effectiveModelContext,
+ SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference();
+ preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, parentSchemaNode, parentNodeXpath.get());
+ xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveStatementInference);
+ } else {
+ preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, schemaContext);
+ xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveModelContext);
+ }
+
+ try (xmlParserStream;
+ StringReader stringReader = new StringReader(preparedXmlContent)) {
+ final XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(stringReader);
+ xmlParserStream.parse(xmlStreamReader);
+ }
+ } catch (final XMLStreamException | URISyntaxException | IOException | SAXException | NullPointerException
+ | ParserConfigurationException | TransformerException exception) {
+ throw new DataValidationException(
+ "Failed to parse xml data: " + xmlData, exception.getMessage(), exception);
}
- return xpathBuilder.toString();
+ final DataContainerChild dataContainerChild =
+ (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult());
+ final YangInstanceIdentifier.NodeIdentifier nodeIdentifier =
+ new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType());
+ return Builders.containerBuilder().withChild(dataContainerChild).withNodeIdentifier(nodeIdentifier).build();
}
+ private static Collection<QName> getDataSchemaNodeIdentifiers(final SchemaContext schemaContext,
+ final String parentNodeXpath) {
+ return (Collection<QName>) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext)
+ .get("dataSchemaNodeIdentifiers");
+ }
private static String getKeyAttributesStatement(
- final YangInstanceIdentifier.NodeIdentifierWithPredicates nodeIdentifier) {
+ final YangInstanceIdentifier.NodeIdentifierWithPredicates nodeIdentifier) {
final List<String> keyAttributes = nodeIdentifier.entrySet().stream().map(
- entry -> {
- final String name = entry.getKey().getLocalName();
- final String value = String.valueOf(entry.getValue()).replace("'", "\\'");
- return String.format("@%s='%s'", name, value);
- }
+ entry -> {
+ final String name = entry.getKey().getLocalName();
+ final String value = String.valueOf(entry.getValue()).replace("'", "\\'");
+ return String.format("@%s='%s'", name, value);
+ }
).collect(Collectors.toList());
if (keyAttributes.isEmpty()) {
@@ -158,26 +266,23 @@ public class YangUtils {
}
}
- private static Collection<QName> getDataSchemaNodeIdentifiersByXpath(final String parentNodeXpath,
- final SchemaContext schemaContext) {
+ private static Map<String, Object> getDataSchemaNodeAndIdentifiersByXpath(final String parentNodeXpath,
+ final SchemaContext schemaContext) {
final String[] xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath);
- return findDataSchemaNodeIdentifiersByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes(),
+ return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes(),
new ArrayList<>());
}
private static String[] xpathToNodeIdSequence(final String xpath) {
- final String[] xpathNodeIdSequence = Arrays.stream(xpath
- .replaceAll(XPATH_NODE_KEY_ATTRIBUTES_REGEX, "")
- .split(XPATH_DELIMITER_REGEX))
- .filter(identifier -> !identifier.isEmpty())
- .toArray(String[]::new);
- if (xpathNodeIdSequence.length < 1) {
- throw new DataValidationException("Invalid xpath.", "Xpath contains no node identifiers.");
+ try {
+ return CpsPathUtil.getXpathNodeIdSequence(xpath);
+ } catch (final PathParsingException pathParsingException) {
+ throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(),
+ pathParsingException);
}
- return xpathNodeIdSequence;
}
- private static Collection<QName> findDataSchemaNodeIdentifiersByXpathNodeIdSequence(
+ private static Map<String, Object> findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(
final String[] xpathNodeIdSequence,
final Collection<? extends DataSchemaNode> dataSchemaNodes,
final Collection<QName> dataSchemaNodeIdentifiers) {
@@ -187,11 +292,15 @@ public class YangUtils {
.findFirst().orElseThrow(() -> schemaNodeNotFoundException(currentXpathNodeId));
dataSchemaNodeIdentifiers.add(currentDataSchemaNode.getQName());
if (xpathNodeIdSequence.length <= 1) {
- return dataSchemaNodeIdentifiers;
+ final Map<String, Object> dataSchemaNodeAndIdentifiers =
+ new HashMap<>();
+ dataSchemaNodeAndIdentifiers.put("dataSchemaNode", currentDataSchemaNode);
+ dataSchemaNodeAndIdentifiers.put("dataSchemaNodeIdentifiers", dataSchemaNodeIdentifiers);
+ return dataSchemaNodeAndIdentifiers;
}
if (currentDataSchemaNode instanceof DataNodeContainer) {
- return findDataSchemaNodeIdentifiersByXpathNodeIdSequence(
- getNextLevelXpathNodeIdSequence(xpathNodeIdSequence),
+ return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(
+ getNextLevelXpathNodeIdSequence(xpathNodeIdSequence),
((DataNodeContainer) currentDataSchemaNode).getChildNodes(),
dataSchemaNodeIdentifiers);
}
@@ -208,4 +317,19 @@ public class YangUtils {
return new DataValidationException("Invalid xpath.",
String.format("No schema node was found for xpath identifier '%s'.", schemaNodeIdentifier));
}
+
+ private static NormalizedNode getFirstChildXmlRoot(final NormalizedNode parent) {
+ final String rootNodeType = parent.getIdentifier().getNodeType().getLocalName();
+ final Collection<DataContainerChild> children = (Collection<DataContainerChild>) parent.body();
+ final Iterator<DataContainerChild> iterator = children.iterator();
+ NormalizedNode child = null;
+ while (iterator.hasNext()) {
+ child = iterator.next();
+ if (!child.getIdentifier().getNodeType().getLocalName().equals(rootNodeType)
+ && !(child instanceof LeafNode)) {
+ return child;
+ }
+ }
+ return getFirstChildXmlRoot(child);
+ }
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
index b60e7e86e0..c81a50ea74 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
@@ -3,7 +3,8 @@
* Copyright (C) 2021-2022 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada.
- * ================================================================================
+ * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022 Deutsche Telekom AG
* 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
@@ -32,6 +33,7 @@ import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.model.Anchor
import org.onap.cps.spi.model.DataNode
import org.onap.cps.spi.model.DataNodeBuilder
+import org.onap.cps.utils.ContentType
import org.onap.cps.yang.YangTextSchemaSourceSet
import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
import spock.lang.Specification
@@ -60,21 +62,59 @@ class CpsDataServiceImplSpec extends Specification {
def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
def observedTimestamp = OffsetDateTime.now()
- def 'Saving json data.'() {
+ def 'Saving multicontainer json data.'() {
given: 'schema set for given anchor and dataspace references test-tree model'
- setupSchemaSetMocks('test-tree.yang')
+ setupSchemaSetMocks('multipleDataTree.yang')
when: 'save data method is invoked with test-tree json data'
- def jsonData = TestUtils.getResourceFileContent('test-tree.json')
+ def jsonData = TestUtils.getResourceFileContent('multiple-object-data.json')
objectUnderTest.saveData(dataspaceName, anchorName, jsonData, observedTimestamp)
then: 'the persistence service method is invoked with correct parameters'
- 1 * mockCpsDataPersistenceService.storeDataNode(dataspaceName, anchorName,
- { dataNode -> dataNode.xpath == '/test-tree' })
+ 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
+ { dataNode -> dataNode.xpath[index] == xpath })
+ and: 'the CpsValidator is called on the dataspaceName and AnchorName'
+ 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
+ and: 'data updated event is sent to notification service'
+ 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/', Operation.CREATE, observedTimestamp)
+ where:
+ index | xpath
+ 0 | '/first-container'
+ 1 | '/last-container'
+
+ }
+
+ def 'Saving #scenario data.'() {
+ given: 'schema set for given anchor and dataspace references test-tree model'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'save data method is invoked with test-tree #scenario data'
+ def data = TestUtils.getResourceFileContent(dataFile)
+ objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp, contentType)
+ then: 'the persistence service method is invoked with correct parameters'
+ 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
+ { dataNode -> dataNode.xpath[0] == '/test-tree' })
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'data updated event is sent to notification service'
1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, '/', Operation.CREATE, observedTimestamp)
+ where: 'given parameters'
+ scenario | dataFile | contentType
+ 'json' | 'test-tree.json' | ContentType.JSON
+ 'xml' | 'test-tree.xml' | ContentType.XML
}
+ def 'Saving #scenarioDesired data with invalid data.'() {
+ given: 'schema set for given anchor and dataspace references test-tree model'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'save data method is invoked with test-tree json data'
+ objectUnderTest.saveData(dataspaceName, anchorName, invalidData, observedTimestamp, contentType)
+ then: 'a data validation exception is thrown'
+ thrown(DataValidationException)
+ where: 'given parameters'
+ scenarioDesired | invalidData | contentType
+ 'json' | '{invalid json' | ContentType.XML
+ 'xml' | '<invalid xml' | ContentType.JSON
+ }
+
+
def 'Saving child data fragment under existing node.'() {
given: 'schema set for given anchor and dataspace references test-tree model'
setupSchemaSetMocks('test-tree.yang')
@@ -82,8 +122,8 @@ class CpsDataServiceImplSpec extends Specification {
def jsonData = '{"branch": [{"name": "New"}]}'
objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
then: 'the persistence service method is invoked with correct parameters'
- 1 * mockCpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, '/test-tree',
- { dataNode -> dataNode.xpath == '/test-tree/branch[@name=\'New\']' })
+ 1 * mockCpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, '/test-tree',
+ { dataNode -> dataNode.xpath[0] == '/test-tree/branch[@name=\'New\']' })
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
and: 'data updated event is sent to notification service'
@@ -207,8 +247,8 @@ class CpsDataServiceImplSpec extends Specification {
when: 'replace data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath'
objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp)
then: 'the persistence service method is invoked with correct parameters'
- 1 * mockCpsDataPersistenceService.updateDataNodeAndDescendants(dataspaceName, anchorName,
- { dataNode -> dataNode.xpath == expectedNodeXpath })
+ 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName,
+ { dataNode -> dataNode.xpath[0] == expectedNodeXpath })
and: 'data updated event is sent to notification service'
1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, parentNodeXpath, Operation.UPDATE, observedTimestamp)
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
index 2fc85aa5a4..ccfb23b449 100755
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy
@@ -3,6 +3,7 @@
* Copyright (C) 2021-2022 Nordix Foundation.
* Modifications Copyright (C) 2021-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
+ * Modifications Copyright (C) 2022 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -90,9 +91,9 @@ class E2ENetworkSliceSpec extends Specification {
when: 'saveData method is invoked'
cpsDataServiceImpl.saveData(dataspaceName, anchorName, jsonData, noTimestamp)
then: 'Parameters are validated and processing is delegated to persistence service'
- 1 * mockDataStoreService.storeDataNode('someDataspace', 'someAnchor', _) >>
+ 1 * mockDataStoreService.storeDataNodes('someDataspace', 'someAnchor', _) >>
{ args -> dataNodeStored = args[2]}
- def child = dataNodeStored.childDataNodes[0]
+ def child = dataNodeStored[0].childDataNodes[0]
assert child.childDataNodes.size() == 1
and: 'list of Tracking Area for a Coverage Area are stored with correct xpath and child nodes '
def listOfTAForCoverageArea = child.childDataNodes[0]
@@ -122,10 +123,10 @@ class E2ENetworkSliceSpec extends Specification {
when: 'saveData method is invoked'
cpsDataServiceImpl.saveData('someDataspace', 'someAnchor', jsonData, noTimestamp)
then: 'parameters are validated and processing is delegated to persistence service'
- 1 * mockDataStoreService.storeDataNode('someDataspace', 'someAnchor', _) >>
+ 1 * mockDataStoreService.storeDataNodes('someDataspace', 'someAnchor', _) >>
{ args -> dataNodeStored = args[2]}
and: 'the size of the tree is correct'
- def cpsRanInventory = TestUtils.getFlattenMapByXpath(dataNodeStored)
+ def cpsRanInventory = TestUtils.getFlattenMapByXpath(dataNodeStored[0])
assert cpsRanInventory.size() == 4
and: 'ran-inventory contains the correct child node'
def ranInventory = cpsRanInventory.get('/ran-inventory')
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
index e46147c04d..1559783e97 100644
--- a/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/spi/model/DataNodeBuilderSpec.groovy
@@ -2,6 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Nordix Foundation.
+ * Modifications Copyright (C) 2022 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,13 +22,10 @@
package org.onap.cps.spi.model
import org.onap.cps.TestUtils
-import org.onap.cps.spi.model.DataNodeBuilder
import org.onap.cps.utils.DataMapUtils
import org.onap.cps.utils.YangUtils
import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
-import org.opendaylight.yangtools.yang.common.QName
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode
import spock.lang.Specification
class DataNodeBuilderSpec extends Specification {
@@ -50,17 +48,17 @@ class DataNodeBuilderSpec extends Specification {
'ietf/ietf-inet-types@2013-07-15.yang'
]
- def 'Converting NormalizedNode (tree) to a DataNode (tree).'() {
+ def 'Converting ContainerNode (tree) to a DataNode (tree).'() {
given: 'the schema context for expected model'
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
- and: 'the json data parsed into normalized node object'
+ and: 'the json data parsed into container node object'
def jsonData = TestUtils.getResourceFileContent('test-tree.json')
- def normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext)
- when: 'the normalized node is converted to a data node'
- def result = new DataNodeBuilder().withNormalizedNodeTree(normalizedNode).build()
+ def containerNode = YangUtils.parseJsonData(jsonData, schemaContext)
+ when: 'the container node is converted to a data node'
+ def result = new DataNodeBuilder().withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
- then: '5 DataNode objects with unique xpath were created in total'
+ then: '6 DataNode objects with unique xpath were created in total'
mappedResult.size() == 6
and: 'all expected xpaths were built'
mappedResult.keySet().containsAll(expectedLeavesByXpathMap.keySet())
@@ -70,16 +68,16 @@ class DataNodeBuilderSpec extends Specification {
}
}
- def 'Converting NormalizedNode (tree) to a DataNode (tree) for known parent node.'() {
+ def 'Converting ContainerNode (tree) to a DataNode (tree) for known parent node.'() {
given: 'a schema context for expected model'
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
- and: 'the json data parsed into normalized node object'
+ and: 'the json data parsed into container node object'
def jsonData = '{ "branch": [{ "name": "Branch", "nest": { "name": "Nest", "birds": ["bird"] } }] }'
- def normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext, "/test-tree")
- when: 'the normalized node is converted to a data node with parent node xpath defined'
+ def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, "/test-tree")
+ when: 'the container node is converted to a data node with parent node xpath defined'
def result = new DataNodeBuilder()
- .withNormalizedNodeTree(normalizedNode)
+ .withContainerNode(containerNode)
.withParentNodeXpath("/test-tree")
.build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
@@ -90,15 +88,15 @@ class DataNodeBuilderSpec extends Specification {
.containsAll(['/test-tree/branch[@name=\'Branch\']', '/test-tree/branch[@name=\'Branch\']/nest'])
}
- def 'Converting NormalizedNode (tree) to a DataNode (tree) -- augmentation case.'() {
+ def 'Converting ContainerNode (tree) to a DataNode (tree) -- augmentation case.'() {
given: 'a schema context for expected model'
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(networkTopologyModelRfc8345)
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
- and: 'the json data parsed into normalized node object'
+ and: 'the json data parsed into container node object'
def jsonData = TestUtils.getResourceFileContent('ietf/data/ietf-network-topology-sample-rfc8345.json')
- def normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext)
- when: 'the normalized node is converted to a data node '
- def result = new DataNodeBuilder().withNormalizedNodeTree(normalizedNode).build()
+ def containerNode = YangUtils.parseJsonData(jsonData, schemaContext)
+ when: 'the container node is converted to a data node '
+ def result = new DataNodeBuilder().withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
then: 'all expected data nodes are populated'
mappedResult.size() == 32
@@ -122,17 +120,17 @@ class DataNodeBuilderSpec extends Specification {
])
}
- def 'Converting NormalizedNode (tree) to a DataNode (tree) for known parent node -- augmentation case.'() {
+ def 'Converting ContainerNode (tree) to a DataNode (tree) for known parent node -- augmentation case.'() {
given: 'a schema context for expected model'
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(networkTopologyModelRfc8345)
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
and: 'parent node xpath referencing augmentation node within a model'
def parentNodeXpath = "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']"
- and: 'the json data fragment parsed into normalized node object for given parent node xpath'
+ and: 'the json data fragment parsed into container node object for given parent node xpath'
def jsonData = '{"source": {"source-node": "D1", "source-tp": "1-2-1"}}'
- def normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath)
- when: 'the normalized node is converted to a data node with given parent node xpath'
- def result = new DataNodeBuilder().withNormalizedNodeTree(normalizedNode)
+ def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath)
+ when: 'the container node is converted to a data node with given parent node xpath'
+ def result = new DataNodeBuilder().withContainerNode(containerNode)
.withParentNodeXpath(parentNodeXpath).build()
then: 'the resulting data node represents a child of augmentation node'
assert result.xpath == "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']/source"
@@ -140,15 +138,15 @@ class DataNodeBuilderSpec extends Specification {
assert result.leaves['source-tp'] == '1-2-1'
}
- def 'Converting NormalizedNode (tree) to a DataNode (tree) -- with ChoiceNode.'() {
+ def 'Converting ContainerNode (tree) to a DataNode (tree) -- with ChoiceNode.'() {
given: 'a schema context for expected model'
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('yang-with-choice-node.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
- and: 'the json data fragment parsed into normalized node object'
+ and: 'the json data fragment parsed into container node object'
def jsonData = TestUtils.getResourceFileContent('data-with-choice-node.json')
- def normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext)
- when: 'the normalized node is converted to a data node'
- def result = new DataNodeBuilder().withNormalizedNodeTree(normalizedNode).build()
+ def containerNode = YangUtils.parseJsonData(jsonData, schemaContext)
+ when: 'the container node is converted to a data node'
+ def result = new DataNodeBuilder().withContainerNode(containerNode).build()
def mappedResult = TestUtils.getFlattenMapByXpath(result)
then: 'the resulting data node contains only one xpath with 3 leaves'
mappedResult.keySet().containsAll([
@@ -159,16 +157,16 @@ class DataNodeBuilderSpec extends Specification {
assert result.leaves['choice-case1-leaf-b'] == "test"
}
- def 'Converting NormalizedNode into DataNode collection: #scenario.'() {
+ def 'Converting ContainerNode into DataNode collection: #scenario.'() {
given: 'a schema context for expected model'
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
and: 'parent node xpath referencing parent of list element'
def parentNodeXpath = "/test-tree"
- and: 'the json data fragment (list element) parsed into normalized node object'
- def normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath)
- when: 'the normalized node is converted to a data node collection'
- def result = new DataNodeBuilder().withNormalizedNodeTree(normalizedNode)
+ and: 'the json data fragment (list element) parsed into container node object'
+ def containerNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath)
+ when: 'the container node is converted to a data node collection'
+ def result = new DataNodeBuilder().withContainerNode(containerNode)
.withParentNodeXpath(parentNodeXpath).buildCollection()
def resultXpaths = result.collect { it.getXpath() }
then: 'the resulting collection contains data nodes for expected list elements'
@@ -180,16 +178,15 @@ class DataNodeBuilderSpec extends Specification {
'multiple entries' | '{"branch": [{"name": "One"}, {"name": "Two"}]}' | 2 | ['/test-tree/branch[@name=\'One\']', '/test-tree/branch[@name=\'Two\']']
}
- def 'Converting NormalizedNode to a DataNode collection -- edge cases: #scenario.'() {
- when: 'the normalized node is #node'
- def result = new DataNodeBuilder().withNormalizedNodeTree(normalizedNode).buildCollection()
+ def 'Converting ContainerNode to a DataNode collection -- edge cases: #scenario.'() {
+ when: 'the container node is #node'
+ def result = new DataNodeBuilder().withContainerNode(containerNode).buildCollection()
then: 'the resulting collection contains data nodes for expected list elements'
- assert result.size() == expectedSize
- assert result.containsAll(expectedNodes)
+ assert result.isEmpty()
where: 'following parameters are used'
- scenario | node | normalizedNode | expectedSize | expectedNodes
- 'NormalizedNode is null' | 'null' | null | 1 | [ new DataNode() ]
- 'NormalizedNode is an unsupported type' | 'not supported' | Mock(NormalizedNode) | 0 | [ ]
+ scenario | containerNode
+ 'ContainerNode is null' | null
+ 'ContainerNode is an unsupported type' | Mock(ContainerNode)
}
def 'Use of adding the module name prefix attribute of data node.'() {
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy
index e205a19eed..b70c437953 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy
@@ -41,7 +41,7 @@ class JsonObjectMapperSpec extends Specification {
then: 'the result is a valid json string (can be parsed)'
def contentMap = new JsonSlurper().parseText(content)
and: 'the parsed content is as expected'
- assert contentMap.'test:bookstore'.'bookstore-name' == 'Chapters'
+ assert contentMap.'test:bookstore'.'bookstore-name' == 'Chapters/Easons'
}
def 'Map a structured object to json String error.'() {
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/JsonParserStreamSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/JsonParserStreamSpec.groovy
index 40f0e0a2ae..2eede23913 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/JsonParserStreamSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/JsonParserStreamSpec.groovy
@@ -1,3 +1,24 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation
+ * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
package org.onap.cps.utils
import com.google.gson.stream.JsonReader
@@ -26,10 +47,10 @@ class JsonParserStreamSpec extends Specification{
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
and: 'variable to store the result of parsing'
DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> builder =
- Builders.containerBuilder().withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(schemaContext.getQName()));
- def normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter.from(builder);
+ Builders.containerBuilder().withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(schemaContext.getQName()))
+ def normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter.from(builder)
def jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02
- .getShared((EffectiveModelContext) schemaContext);
+ .getShared((EffectiveModelContext) schemaContext)
and: 'JSON parser stream'
def jsonParserStream = JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory)
when: 'parsing is invoked with the given JSON reader'
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
new file mode 100644
index 0000000000..b044e2e727
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/XmlFileUtilsSpec.groovy
@@ -0,0 +1,61 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Deutsche Telekom AG
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.cps.utils
+
+import org.onap.cps.TestUtils
+import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
+import spock.lang.Specification
+
+class XmlFileUtilsSpec extends Specification {
+ def 'Parse a valid xml content #scenario'(){
+ given: 'YANG model schema context'
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+ when: 'the XML data is parsed'
+ def parsedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, schemaContext)
+ then: 'the result XML is wrapped by root node defined in YANG schema'
+ assert parsedXmlContent == expectedOutput
+ where:
+ scenario | xmlData || expectedOutput
+ 'without root data node' | '<?xml version="1.0" encoding="UTF-8"?><class> </class>' || '<?xml version="1.0" encoding="UTF-8"?><stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><class> </class></stores>'
+ 'with root data node' | '<?xml version="1.0" encoding="UTF-8"?><stores><class> </class></stores>' || '<?xml version="1.0" encoding="UTF-8"?><stores><class> </class></stores>'
+ 'no xml header' | '<stores><class> </class></stores>' || '<stores><class> </class></stores>'
+ }
+
+ def 'Parse a xml content with XPath container #scenario'() {
+ given: 'YANG model schema context'
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+ and: 'Parent schema node by xPath'
+ def parentSchemaNode = YangUtils.getDataSchemaNodeAndIdentifiersByXpath(xPath, schemaContext)
+ .get("dataSchemaNode")
+ when: 'the XML data is parsed'
+ def parsedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, parentSchemaNode, xPath)
+ then: 'the result XML is wrapped by xPath defined parent root node'
+ assert parsedXmlContent == expectedOutput
+ where:
+ scenario | xmlData | xPath || expectedOutput
+ 'XML element test tree' | '<?xml version="1.0" encoding="UTF-8"?><test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch></test-tree>' | '/test-tree' || '<?xml version="1.0" encoding="UTF-8"?><test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch></test-tree>'
+ 'without root data node' | '<?xml version="1.0" encoding="UTF-8"?><nest xmlns="org:onap:cps:test:test-tree"><name>Small</name><birds>Sparrow</birds></nest>' | '/test-tree/branch[@name=\'Branch\']' || '<?xml version="1.0" encoding="UTF-8"?><branch xmlns="org:onap:cps:test:test-tree"><name>Branch</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch>'
+
+
+ }
+
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
index 65aa3af7d8..bf6e134a65 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
@@ -2,6 +2,8 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2020-2022 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
+ * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022 Deutsche Telekom AG
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,16 +31,42 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode
import spock.lang.Specification
class YangUtilsSpec extends Specification {
- def 'Parsing a valid Json String.'() {
+ def 'Parsing a valid multicontainer Json String.'() {
given: 'a yang model (file)'
- def jsonData = org.onap.cps.TestUtils.getResourceFileContent('bookstore.json')
+ def jsonData = org.onap.cps.TestUtils.getResourceFileContent('multiple-object-data.json')
and: 'a model for that data'
- def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('multipleDataTree.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
when: 'the json data is parsed'
- NormalizedNode result = YangUtils.parseJsonData(jsonData, schemaContext)
+ def result = YangUtils.parseJsonData(jsonData, schemaContext)
+ then: 'a ContainerNode holding collection of normalized nodes is returned'
+ result.body().getAt(index) instanceof NormalizedNode == true
+ then: 'qualified name of children created is as expected'
+ result.body().getAt(index).getIdentifier().nodeType == QName.create('org:onap:ccsdk:multiDataTree', '2020-09-15', nodeName)
+ where:
+ index | nodeName
+ 0 | 'first-container'
+ 1 | 'last-container'
+ }
+
+ def 'Parsing a valid #scenario String.'() {
+ given: 'a yang model (file)'
+ def fileData = org.onap.cps.TestUtils.getResourceFileContent(contentFile)
+ and: 'a model for that data'
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+ when: 'the data is parsed'
+ NormalizedNode result = YangUtils.parseData(contentType, fileData, schemaContext)
then: 'the result is a normalized node of the correct type'
- result.getIdentifier().nodeType == QName.create('org:onap:ccsdk:sample', '2020-09-15', 'bookstore')
+ if (revision) {
+ result.identifier.nodeType == QName.create(namespace, revision, localName)
+ } else {
+ result.identifier.nodeType == QName.create(namespace, localName)
+ }
+ where:
+ scenario | contentFile | contentType | namespace | revision | localName
+ 'JSON' | 'bookstore.json' | ContentType.JSON | 'org:onap:ccsdk:sample' | '2020-09-15' | 'bookstore'
+ 'XML' | 'bookstore.xml' | ContentType.XML | 'urn:ietf:params:xml:ns:netconf:base:1.0' | '' | 'bookstore'
}
def 'Parsing invalid data: #description.'() {
@@ -46,29 +74,37 @@ class YangUtilsSpec extends Specification {
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('bookstore.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
when: 'invalid data is parsed'
- YangUtils.parseJsonData(invalidJson, schemaContext)
+ YangUtils.parseData(contentType, invalidData, schemaContext)
then: 'an exception is thrown'
thrown(DataValidationException)
- where: 'the following invalid json is provided'
- invalidJson | description
- '{incomplete json' | 'incomplete json'
- '{"test:bookstore": {"address": "Parnell st." }}' | 'json with un-modelled data'
- '{" }' | 'json with syntax exception'
+ where: 'the following invalid data is provided'
+ invalidData | contentType | description
+ '{incomplete json' | ContentType.JSON | 'incomplete json'
+ '{"test:bookstore": {"address": "Parnell st." }}' | ContentType.JSON | 'json with un-modelled data'
+ '{" }' | ContentType.JSON | 'json with syntax exception'
+ '<data>' | ContentType.XML | 'incomplete xml'
+ '<data><bookstore><bookstore-anything>blabla</bookstore-anything></bookstore</data>' | ContentType.XML | 'xml with invalid model'
+ '' | ContentType.XML | 'empty xml'
}
- def 'Parsing json data fragment by xpath for #scenario.'() {
+ def 'Parsing data fragment by xpath for #scenario.'() {
given: 'schema context'
def yangResourcesMap = TestUtils.getYangResourcesAsMap('test-tree.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesMap).getSchemaContext()
when: 'json string is parsed'
- def result = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath)
+ def result = YangUtils.parseData(contentType, nodeData, schemaContext, parentNodeXpath)
+ then: 'a ContainerNode holding collection of normalized nodes is returned'
+ result.body().getAt(0) instanceof NormalizedNode == true
then: 'result represents a node of expected type'
- result.getIdentifier().nodeType == QName.create('org:onap:cps:test:test-tree', '2020-02-02', nodeName)
+ result.body().getAt(0).getIdentifier().nodeType == QName.create('org:onap:cps:test:test-tree', '2020-02-02', nodeName)
where:
- scenario | jsonData | parentNodeXpath || nodeName
- 'list element as container' | '{ "branch": { "name": "B", "nest": { "name": "N", "birds": ["bird"] } } }' | '/test-tree' || 'branch'
- 'list element within list' | '{ "branch": [{ "name": "B", "nest": { "name": "N", "birds": ["bird"] } }] }' | '/test-tree' || 'branch'
- 'container element' | '{ "nest": { "name": "N", "birds": ["bird"] } }' | '/test-tree/branch[@name=\'Branch\']' || 'nest'
+ scenario | contentType | nodeData | parentNodeXpath || nodeName
+ 'JSON list element as container' | ContentType.JSON | '{ "branch": { "name": "B", "nest": { "name": "N", "birds": ["bird"] } } }' | '/test-tree' || 'branch'
+ 'JSON list element within list' | ContentType.JSON | '{ "branch": [{ "name": "B", "nest": { "name": "N", "birds": ["bird"] } }] }' | '/test-tree' || 'branch'
+ 'JSON container element' | ContentType.JSON | '{ "nest": { "name": "N", "birds": ["bird"] } }' | '/test-tree/branch[@name=\'Branch\']' || 'nest'
+ 'XML element test tree' | ContentType.XML | '<?xml version=\'1.0\' encoding=\'UTF-8\'?><branch xmlns="org:onap:cps:test:test-tree"><name>Left</name><nest><name>Small</name><birds>Sparrow</birds></nest></branch>' | '/test-tree' || 'branch'
+ 'XML element branch xpath' | ContentType.XML | '<?xml version=\'1.0\' encoding=\'UTF-8\'?><branch xmlns="org:onap:cps:test:test-tree"><name>Left</name><nest><name>Small</name><birds>Sparrow</birds><birds>Robin</birds></nest></branch>' | '/test-tree' || 'branch'
+ 'XML container element' | ContentType.XML | '<?xml version=\'1.0\' encoding=\'UTF-8\'?><nest xmlns="org:onap:cps:test:test-tree"><name>Small</name><birds>Sparrow</birds></nest>' | '/test-tree/branch[@name=\'Branch\']' || 'nest'
}
def 'Parsing json data fragment by xpath error scenario: #scenario.'() {
@@ -126,5 +162,4 @@ class YangUtilsSpec extends Specification {
'xpath contains list attribute' | '/test-tree/branch[@name=\'Branch\']' || ['test-tree','branch']
'xpath contains list attributes with /' | '/test-tree/branch[@name=\'/Branch\']/categories[@id=\'/broken\']' || ['test-tree','branch','categories']
}
-
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy
index 236221aca7..6d570d6432 100644
--- a/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/yang/YangTextSchemaSourceSetBuilderSpec.groovy
@@ -3,6 +3,7 @@
* Copyright (C) 2020-2021 Pantheon.tech
* Modifications Copyright (C) 2020-2022 Nordix Foundation
* Modifications Copyright (C) 2021 Bell Canada.
+ * Modifications Copyright (C) 2022 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,11 +21,12 @@
* ============LICENSE_END=========================================================
*/
-package org.onap.cps.yang
+package org.onap.cps.utils.yang
import org.onap.cps.TestUtils
import org.onap.cps.spi.exceptions.ModelValidationException
+import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
import org.opendaylight.yangtools.yang.common.Revision
import spock.lang.Specification
diff --git a/cps-service/src/test/resources/bookstore.json b/cps-service/src/test/resources/bookstore.json
index d1b8d6882d..459908bd63 100644
--- a/cps-service/src/test/resources/bookstore.json
+++ b/cps-service/src/test/resources/bookstore.json
@@ -1,19 +1,19 @@
{
"test:bookstore":{
- "bookstore-name": "Chapters",
+ "bookstore-name": "Chapters/Easons",
"categories": [
{
- "code": "01",
+ "code": "01/1",
"name": "SciFi",
"books": [
{
"authors": [
"Iain M. Banks"
],
- "lang": "en",
+ "lang": "en/it",
"price": "895",
"pub_year": "1994",
- "title": "Feersum Endjinn"
+ "title": "Feersum Endjinn/Endjinn Feersum"
},
{
"authors": [
diff --git a/cps-service/src/test/resources/bookstore.xml b/cps-service/src/test/resources/bookstore.xml
new file mode 100644
index 0000000000..dd45e16896
--- /dev/null
+++ b/cps-service/src/test/resources/bookstore.xml
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<stores xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+<bookstore xmlns="org:onap:ccsdk:sample">
+ <bookstore-name>Chapters</bookstore-name>
+ <categories>
+ <code>1</code>
+ <name>SciFi</name>
+ <books>
+ <title>2001: A Space Odyssey</title>
+ <lang>en</lang>
+ <authors>
+ Iain M. Banks
+ </authors>
+ <pub_year>1994</pub_year>
+ <price>895</price>
+ </books>
+ </categories>
+</bookstore>
+</stores> \ No newline at end of file
diff --git a/cps-service/src/test/resources/bookstore_xpath.xml b/cps-service/src/test/resources/bookstore_xpath.xml
new file mode 100644
index 0000000000..e206901d6d
--- /dev/null
+++ b/cps-service/src/test/resources/bookstore_xpath.xml
@@ -0,0 +1,17 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<bookstore xmlns="org:onap:ccsdk:sample">
+ <bookstore-name>Chapters</bookstore-name>
+ <categories>
+ <code>1</code>
+ <name>SciFi</name>
+ <books>
+ <title>2001: A Space Odyssey</title>
+ <lang>en</lang>
+ <authors>
+ Iain M. Banks
+ </authors>
+ <pub_year>1994</pub_year>
+ <price>895</price>
+ </books>
+ </categories>
+</bookstore> \ No newline at end of file
diff --git a/cps-service/src/test/resources/test-tree.xml b/cps-service/src/test/resources/test-tree.xml
new file mode 100644
index 0000000000..3daa814cf6
--- /dev/null
+++ b/cps-service/src/test/resources/test-tree.xml
@@ -0,0 +1,27 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <test-tree xmlns="org:onap:cps:test:test-tree">
+ <branch>
+ <name>Left</name>
+ <nest>
+ <name>Small</name>
+ <birds>Sparrow</birds>
+ <birds>Robin</birds>
+ <birds>Finch</birds>
+ </nest>
+ </branch>
+ <branch>
+ <name>Right</name>
+ <nest>
+ <name>Big</name>
+ <birds>Owl</birds>
+ <birds>Raven</birds>
+ <birds>Crow</birds>
+ </nest>
+ </branch>
+ <fruit>
+ <name>Apple</name>
+ <color>Green</color>
+ </fruit>
+ </test-tree>
+</data>