aboutsummaryrefslogtreecommitdiffstats
path: root/cps-service/src
diff options
context:
space:
mode:
authorArpit Singh <AS00745003@techmahindra.com>2024-11-07 15:33:39 +0530
committerArpit Singh <AS00745003@techmahindra.com>2025-03-12 09:34:37 +0530
commit646caa6ccba0eda7d9a681761ae03df5d2c45f1d (patch)
tree35e488e6860c6629d32c350b37178859cb8eae24 /cps-service/src
parentab96e052a6725ad0cc5e56ae69461c352c83551f (diff)
Refactor buildDataNodes to a separate service
- Moved the code for buildDataNodes from CpsDataServiceImpl.java to a separate service named DataNodeBuilderService.java - Renamed the methods to be clear and in-line with their intended use in DataNodeBuilderService class - Moved ROOT_NODE_XPATH and NO_PARENT_PATH to CpsPathUtils Issue-ID: CPS-2487 Change-Id: I46cf843ab79b1e2547d968fbd30528270b95cc16 Signed-off-by: Arpit Singh <AS00745003@techmahindra.com>
Diffstat (limited to 'cps-service/src')
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/DataNodeFactory.java83
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java163
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/DataNodeFactoryImpl.java107
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy32
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/DataNodeFactorySpec.groovy196
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/impl/E2ENetworkSliceSpec.groovy5
6 files changed, 441 insertions, 145 deletions
diff --git a/cps-service/src/main/java/org/onap/cps/api/DataNodeFactory.java b/cps-service/src/main/java/org/onap/cps/api/DataNodeFactory.java
new file mode 100644
index 0000000000..1e3410c7f4
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/api/DataNodeFactory.java
@@ -0,0 +1,83 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 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.api;
+
+import java.util.Collection;
+import java.util.Map;
+import org.onap.cps.api.model.Anchor;
+import org.onap.cps.api.model.DataNode;
+import org.onap.cps.utils.ContentType;
+
+public interface DataNodeFactory {
+
+ /**
+ * Create data nodes using an anchor, xpath, and JSON/XML string.
+ *
+ * @param anchor name of Anchor sharing same schema structure as the JSON/XML string
+ * @param xpath xpath of the data node
+ * @param nodeData JSON/XML data string
+ * @param contentType JSON or XML content type
+ * @return a collection of {@link DataNode}
+ */
+ Collection<DataNode> createDataNodesWithAnchorXpathAndNodeData(Anchor anchor, String xpath,
+ String nodeData, ContentType contentType);
+
+ /**
+ * Create data nodes using an anchor, parent data node xpath, and JSON/XML string.
+ *
+ * @param anchor name of Anchor sharing same schema structure as the JSON/XML string
+ * @param parentNodeXpath xpath of the parent data node
+ * @param nodeData JSON/XML data string
+ * @param contentType JSON or XML content type
+ * @return a collection of {@link DataNode}
+ */
+ Collection<DataNode> createDataNodesWithAnchorParentXpathAndNodeData(Anchor anchor,
+ String parentNodeXpath,
+ String nodeData,
+ ContentType contentType);
+
+ /**
+ * Create data nodes using a map of xpath to JSON/XML data, and anchor name.
+ *
+ * @param anchor name of Anchor sharing same schema structure as the JSON/XML string
+ * @param nodesData map of xpath and node JSON/XML data
+ * @param contentType JSON or XML content type
+ * @return a collection of {@link DataNode}
+ */
+ Collection<DataNode> createDataNodesWithAnchorAndXpathToNodeData(Anchor anchor,
+ Map<String, String> nodesData,
+ ContentType contentType);
+
+ /**
+ * Create data nodes using a map of YANG resource name to content, xpath, and JSON/XML string.
+ *
+ * @param yangResourcesNameToContentMap map of YANG resource name to content
+ * @param xpath xpath of the data node
+ * @param nodeData JSON/XML data string
+ * @param contentType JSON or XML content type
+ * @return a collection of {@link DataNode}
+ */
+ Collection<DataNode> createDataNodesWithYangResourceXpathAndNodeData(
+ Map<String, String> yangResourcesNameToContentMap,
+ String xpath, String nodeData,
+ ContentType contentType);
+
+}
diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java
index 9f70ac9132..ab6f7a2df8 100644
--- a/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java
@@ -3,7 +3,7 @@
* Copyright (C) 2021-2024 Nordix Foundation
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2025 TechMahindra Ltd.
* Modifications Copyright (C) 2022 Deutsche Telekom AG
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,6 +24,9 @@
package org.onap.cps.impl;
+import static org.onap.cps.cpspath.parser.CpsPathUtil.NO_PARENT_PATH;
+import static org.onap.cps.cpspath.parser.CpsPathUtil.ROOT_NODE_XPATH;
+
import io.micrometer.core.annotation.Timed;
import java.io.Serializable;
import java.time.OffsetDateTime;
@@ -39,7 +42,7 @@ import lombok.extern.slf4j.Slf4j;
import org.onap.cps.api.CpsAnchorService;
import org.onap.cps.api.CpsDataService;
import org.onap.cps.api.CpsDeltaService;
-import org.onap.cps.api.exceptions.DataValidationException;
+import org.onap.cps.api.DataNodeFactory;
import org.onap.cps.api.model.Anchor;
import org.onap.cps.api.model.DataNode;
import org.onap.cps.api.model.DeltaReport;
@@ -54,7 +57,6 @@ import org.onap.cps.utils.DataMapUtils;
import org.onap.cps.utils.JsonObjectMapper;
import org.onap.cps.utils.PrefixResolver;
import org.onap.cps.utils.YangParser;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.springframework.stereotype.Service;
@Service
@@ -62,14 +64,12 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class CpsDataServiceImpl implements CpsDataService {
- private static final String ROOT_NODE_XPATH = "/";
- private static final String PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH = "";
private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L;
- private static final String NO_DATA_NODES = "No data nodes.";
private final CpsDataPersistenceService cpsDataPersistenceService;
private final CpsDataUpdateEventsService cpsDataUpdateEventsService;
private final CpsAnchorService cpsAnchorService;
+ private final DataNodeFactory dataNodeFactory;
private final CpsValidator cpsValidator;
private final YangParser yangParser;
@@ -90,8 +90,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodes =
- buildDataNodesWithParentNodeXpath(anchor, ROOT_NODE_XPATH, nodeData, contentType);
+ final Collection<DataNode> dataNodes = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, ROOT_NODE_XPATH, nodeData, contentType);
cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes);
sendDataUpdatedEvent(anchor, ROOT_NODE_XPATH, Operation.CREATE, observedTimestamp);
}
@@ -110,8 +110,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodes =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
+ final Collection<DataNode> dataNodes = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, nodeData, contentType);
cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.CREATE, observedTimestamp);
}
@@ -124,9 +124,9 @@ public class CpsDataServiceImpl implements CpsDataService {
final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> listElementDataNodeCollection =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
- if (isRootNodeXpath(parentNodeXpath)) {
+ final Collection<DataNode> listElementDataNodeCollection = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, nodeData, contentType);
+ if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, listElementDataNodeCollection);
} else {
cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
@@ -163,8 +163,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final String nodeData, final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodesInPatch =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
+ final Collection<DataNode> dataNodesInPatch = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, nodeData, contentType);
final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream()
.collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves));
cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves);
@@ -180,8 +180,9 @@ public class CpsDataServiceImpl implements CpsDataService {
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodeUpdates =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON);
+ final Collection<DataNode> dataNodeUpdates = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, dataNodeUpdatesAsJson,
+ ContentType.JSON);
for (final DataNode dataNodeUpdate : dataNodeUpdates) {
processDataNodeUpdate(anchor, dataNodeUpdate);
}
@@ -256,8 +257,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodes =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
+ final Collection<DataNode> dataNodes = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, nodeData, contentType);
cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
sendDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp);
}
@@ -266,13 +267,14 @@ public class CpsDataServiceImpl implements CpsDataService {
@Timed(value = "cps.data.service.datanode.descendants.batch.update",
description = "Time taken to update a batch of data nodes and descendants")
public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
- final Map<String, String> nodeDataPerXPath,
+ final Map<String, String> nodeDataPerParentNodeXPath,
final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> dataNodes = buildDataNodesWithParentNodeXpath(anchor, nodeDataPerXPath, contentType);
+ final Collection<DataNode> dataNodes = dataNodeFactory
+ .createDataNodesWithAnchorAndXpathToNodeData(anchor, nodeDataPerParentNodeXPath, contentType);
cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes);
- nodeDataPerXPath.keySet().forEach(nodeXpath ->
+ nodeDataPerParentNodeXPath.keySet().forEach(nodeXpath ->
sendDataUpdatedEvent(anchor, nodeXpath, Operation.UPDATE, observedTimestamp));
}
@@ -283,8 +285,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final String nodeData, final OffsetDateTime observedTimestamp, final ContentType contentType) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final Collection<DataNode> newListElements =
- buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
+ final Collection<DataNode> newListElements = dataNodeFactory
+ .createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentNodeXpath, nodeData, contentType);
replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
}
@@ -362,7 +364,7 @@ public class CpsDataServiceImpl implements CpsDataService {
public void validateData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String nodeData, final ContentType contentType) {
final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
- final String xpath = ROOT_NODE_XPATH.equals(parentNodeXpath) ? PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH :
+ final String xpath = ROOT_NODE_XPATH.equals(parentNodeXpath) ? NO_PARENT_PATH :
CpsPathUtil.getNormalizedXpath(parentNodeXpath);
yangParser.validateData(contentType, nodeData, anchor, xpath);
}
@@ -373,8 +375,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final Collection<DataNode> sourceDataNodesRebuilt = new ArrayList<>();
if (sourceDataNodes != null) {
final String sourceDataNodesAsJson = getDataNodesAsJson(sourceAnchor, sourceDataNodes);
- sourceDataNodesRebuilt.addAll(
- buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, sourceDataNodesAsJson, ContentType.JSON));
+ sourceDataNodesRebuilt.addAll(dataNodeFactory.createDataNodesWithAnchorXpathAndNodeData(
+ sourceAnchor, xpath, sourceDataNodesAsJson, ContentType.JSON));
}
return sourceDataNodesRebuilt;
}
@@ -383,10 +385,12 @@ public class CpsDataServiceImpl implements CpsDataService {
final Map<String, String> yangResourceContentPerName,
final String targetData) {
if (yangResourceContentPerName.isEmpty()) {
- return buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, targetData, ContentType.JSON);
+ return dataNodeFactory
+ .createDataNodesWithAnchorXpathAndNodeData(sourceAnchor, xpath, targetData, ContentType.JSON);
} else {
- return buildDataNodesWithYangResourceAndXpath(yangResourceContentPerName, xpath,
- targetData, ContentType.JSON);
+ return dataNodeFactory
+ .createDataNodesWithYangResourceXpathAndNodeData(yangResourceContentPerName, xpath,
+ targetData, ContentType.JSON);
}
}
@@ -415,105 +419,6 @@ public class CpsDataServiceImpl implements CpsDataService {
return prefixToDataNodes;
}
- private Collection<DataNode> buildDataNodesWithParentNodeXpath(final Anchor anchor,
- final Map<String, String> nodesJsonData,
- final ContentType contentType) {
- final Collection<DataNode> dataNodes = new ArrayList<>();
- for (final Map.Entry<String, String> nodeJsonData : nodesJsonData.entrySet()) {
- dataNodes.addAll(buildDataNodesWithParentNodeXpath(anchor, nodeJsonData.getKey(),
- nodeJsonData.getValue(), contentType));
- }
- return dataNodes;
- }
-
- private Collection<DataNode> buildDataNodesWithParentNodeXpath(final Anchor anchor, final String parentNodeXpath,
- final String nodeData, final ContentType contentType) {
-
- if (ROOT_NODE_XPATH.equals(parentNodeXpath)) {
- final ContainerNode containerNode = yangParser.parseData(contentType, nodeData,
- anchor, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH);
- final Collection<DataNode> dataNodes = new DataNodeBuilder()
- .withContainerNode(containerNode)
- .buildCollection();
- if (dataNodes.isEmpty()) {
- throw new DataValidationException(NO_DATA_NODES, "No data nodes provided");
- }
- return dataNodes;
- }
- final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(parentNodeXpath);
- final ContainerNode containerNode =
- yangParser.parseData(contentType, nodeData, anchor, normalizedParentNodeXpath);
- final Collection<DataNode> dataNodes = new DataNodeBuilder()
- .withParentNodeXpath(normalizedParentNodeXpath)
- .withContainerNode(containerNode)
- .buildCollection();
- if (dataNodes.isEmpty()) {
- throw new DataValidationException(NO_DATA_NODES, "No data nodes provided");
- }
- return dataNodes;
- }
-
- private Collection<DataNode> buildDataNodesWithParentNodeXpath(
- final Map<String, String> yangResourceContentPerName, final String xpath,
- final String nodeData, final ContentType contentType) {
-
- if (isRootNodeXpath(xpath)) {
- final ContainerNode containerNode = yangParser.parseData(contentType, nodeData,
- yangResourceContentPerName, PARENT_NODE_XPATH_FOR_ROOT_NODE_XPATH);
- final Collection<DataNode> dataNodes = new DataNodeBuilder()
- .withContainerNode(containerNode)
- .buildCollection();
- if (dataNodes.isEmpty()) {
- throw new DataValidationException(NO_DATA_NODES, "Data nodes were not found under the xpath " + xpath);
- }
- return dataNodes;
- }
- final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(xpath);
- final ContainerNode containerNode =
- yangParser.parseData(contentType, nodeData, yangResourceContentPerName, normalizedParentNodeXpath);
- final Collection<DataNode> dataNodes = new DataNodeBuilder()
- .withParentNodeXpath(normalizedParentNodeXpath)
- .withContainerNode(containerNode)
- .buildCollection();
- if (dataNodes.isEmpty()) {
- throw new DataValidationException(NO_DATA_NODES, "Data nodes were not found under the xpath " + xpath);
- }
- return dataNodes;
- }
-
- private Collection<DataNode> buildDataNodesWithAnchorAndXpath(final Anchor anchor, final String xpath,
- final String nodeData,
- final ContentType contentType) {
-
- if (!isRootNodeXpath(xpath)) {
- final String parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(xpath);
- if (parentNodeXpath.isEmpty()) {
- return buildDataNodesWithParentNodeXpath(anchor, ROOT_NODE_XPATH, nodeData, contentType);
- }
- return buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
- }
- return buildDataNodesWithParentNodeXpath(anchor, xpath, nodeData, contentType);
- }
-
- private Collection<DataNode> buildDataNodesWithYangResourceAndXpath(
- final Map<String, String> yangResourceContentPerName, final String xpath,
- final String nodeData, final ContentType contentType) {
- if (!isRootNodeXpath(xpath)) {
- final String parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(xpath);
- if (parentNodeXpath.isEmpty()) {
- return buildDataNodesWithParentNodeXpath(yangResourceContentPerName, ROOT_NODE_XPATH,
- nodeData, contentType);
- }
- return buildDataNodesWithParentNodeXpath(yangResourceContentPerName, parentNodeXpath,
- nodeData, contentType);
- }
- return buildDataNodesWithParentNodeXpath(yangResourceContentPerName, xpath, nodeData, contentType);
- }
-
- private static boolean isRootNodeXpath(final String xpath) {
- return ROOT_NODE_XPATH.equals(xpath);
- }
-
private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
cpsDataPersistenceService.batchUpdateDataLeaves(anchor.getDataspaceName(), anchor.getName(),
Collections.singletonMap(dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves()));
diff --git a/cps-service/src/main/java/org/onap/cps/impl/DataNodeFactoryImpl.java b/cps-service/src/main/java/org/onap/cps/impl/DataNodeFactoryImpl.java
new file mode 100644
index 0000000000..76db887c8e
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/impl/DataNodeFactoryImpl.java
@@ -0,0 +1,107 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 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.impl;
+
+import static org.onap.cps.cpspath.parser.CpsPathUtil.NO_PARENT_PATH;
+import static org.onap.cps.cpspath.parser.CpsPathUtil.ROOT_NODE_XPATH;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.api.DataNodeFactory;
+import org.onap.cps.api.exceptions.DataValidationException;
+import org.onap.cps.api.model.Anchor;
+import org.onap.cps.api.model.DataNode;
+import org.onap.cps.cpspath.parser.CpsPathUtil;
+import org.onap.cps.utils.ContentType;
+import org.onap.cps.utils.YangParser;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DataNodeFactoryImpl implements DataNodeFactory {
+
+ private final YangParser yangParser;
+
+ @Override
+ public Collection<DataNode> createDataNodesWithAnchorAndXpathToNodeData(final Anchor anchor,
+ final Map<String, String> nodesDataPerParentNodeXpath,
+ final ContentType contentType) {
+ final Collection<DataNode> dataNodes = new ArrayList<>();
+ for (final Map.Entry<String, String> nodeDataToParentNodeXpath : nodesDataPerParentNodeXpath.entrySet()) {
+ dataNodes.addAll(createDataNodesWithAnchorParentXpathAndNodeData(anchor, nodeDataToParentNodeXpath.getKey(),
+ nodeDataToParentNodeXpath.getValue(), contentType));
+ }
+ return dataNodes;
+ }
+
+ @Override
+ public Collection<DataNode> createDataNodesWithAnchorXpathAndNodeData(final Anchor anchor, final String xpath,
+ final String nodeData,
+ final ContentType contentType) {
+ final String xpathToBuildNodes = isRootNodeXpath(xpath) ? NO_PARENT_PATH :
+ CpsPathUtil.getNormalizedParentXpath(xpath);
+ final ContainerNode containerNode = yangParser.parseData(contentType, nodeData, anchor, xpathToBuildNodes);
+ return convertToDataNodes(xpathToBuildNodes, containerNode);
+ }
+
+ @Override
+ public Collection<DataNode> createDataNodesWithAnchorParentXpathAndNodeData(final Anchor anchor,
+ final String parentNodeXpath,
+ final String nodeData,
+ final ContentType contentType) {
+
+ final String normalizedParentNodeXpath = CpsPathUtil.getNormalizedXpath(parentNodeXpath);
+ final ContainerNode containerNode =
+ yangParser.parseData(contentType, nodeData, anchor, normalizedParentNodeXpath);
+ return convertToDataNodes(normalizedParentNodeXpath, containerNode);
+ }
+
+ @Override
+ public Collection<DataNode> createDataNodesWithYangResourceXpathAndNodeData(
+ final Map<String, String> yangResourceContentPerName,
+ final String xpath, final String nodeData,
+ final ContentType contentType) {
+ final String normalizedParentNodeXpath = isRootNodeXpath(xpath) ? NO_PARENT_PATH :
+ CpsPathUtil.getNormalizedParentXpath(xpath);
+ final ContainerNode containerNode =
+ yangParser.parseData(contentType, nodeData, yangResourceContentPerName, normalizedParentNodeXpath);
+ return convertToDataNodes(normalizedParentNodeXpath, containerNode);
+ }
+
+ private static Collection<DataNode> convertToDataNodes(final String normalizedParentNodeXpath,
+ final ContainerNode containerNode) {
+ final Collection<DataNode> dataNodes = new DataNodeBuilder()
+ .withParentNodeXpath(normalizedParentNodeXpath)
+ .withContainerNode(containerNode)
+ .buildCollection();
+ if (dataNodes.isEmpty()) {
+ throw new DataValidationException("No Data Nodes", "The request did not return any data nodes for xpath "
+ + normalizedParentNodeXpath);
+ }
+ return dataNodes;
+ }
+
+ private static boolean isRootNodeXpath(final String xpath) {
+ return ROOT_NODE_XPATH.equals(xpath);
+ }
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy
index abcda6c696..6b90e557dc 100644
--- a/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy
@@ -3,7 +3,7 @@
* Copyright (C) 2021-2024 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada.
- * Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2025 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.
@@ -68,9 +68,10 @@ class CpsDataServiceImplSpec extends Specification {
def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService)
def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
def mockPrefixResolver = Mock(PrefixResolver)
+ def dataNodeFactory = new DataNodeFactoryImpl(yangParser)
def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockDataUpdateEventsService, mockCpsAnchorService,
- mockCpsValidator, yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)
+ dataNodeFactory, mockCpsValidator, yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)
def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class)
def loggingListAppender
@@ -107,8 +108,9 @@ class CpsDataServiceImplSpec extends Specification {
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'
+ and: 'JSON/XML data is fetched from resource file'
def data = TestUtils.getResourceFileContent(dataFile)
+ when: 'save data method is invoked with test-tree #scenario data'
objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp, contentType)
then: 'the persistence service method is invoked with correct parameters'
1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
@@ -131,7 +133,7 @@ class CpsDataServiceImplSpec extends Specification {
assert exceptionThrown.message.startsWith(expectedMessage)
where: 'given parameters'
scenario | invalidData | contentType || expectedMessage
- 'no data nodes' | '{}' | ContentType.JSON || 'No data nodes'
+ 'no data nodes' | '{}' | ContentType.JSON || 'No Data Nodes'
'invalid json' | '{invalid json' | ContentType.JSON || 'Data Validation Failed'
'invalid xml' | '<invalid xml' | ContentType.XML || 'Data Validation Failed'
}
@@ -139,8 +141,9 @@ class CpsDataServiceImplSpec extends Specification {
def 'Saving list element data fragment under Root node.'() {
given: 'schema set for given anchor and dataspace references bookstore model'
setupSchemaSetMocks('bookstore.yang')
- when: 'save data method is invoked with list element json data'
+ and: 'JSON data associated with bookstore model'
def jsonData = '{"bookstore-address":[{"bookstore-name":"Easons","address":"Dublin,Ireland","postal-code":"D02HA21"}]}'
+ when: 'save data method is invoked with list element json data'
objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp, ContentType.JSON)
then: 'the persistence service method is invoked with correct parameters'
1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
@@ -159,8 +162,8 @@ class CpsDataServiceImplSpec extends Specification {
def 'Saving child data fragment under existing node.'() {
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'
def jsonData = '{"branch": [{"name": "New"}]}'
+ when: 'save data method is invoked with test-tree json data'
objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
then: 'the persistence service method is invoked with correct parameters'
1 * mockCpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, '/test-tree',
@@ -169,7 +172,7 @@ class CpsDataServiceImplSpec extends Specification {
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
}
- def 'Saving list element data fragment under existing JSON/XML node.'() {
+ def 'Saving list element data fragment under existing #scenario .'() {
given: 'schema set for given anchor and dataspace references test-tree model'
setupSchemaSetMocks('test-tree.yang')
when: 'save data method is invoked with list element data'
@@ -187,12 +190,13 @@ class CpsDataServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName and AnchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
where:
- data | contentType
- '{"branch": [{"name": "A"}, {"name": "B"}]}' | ContentType.JSON
- '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>A</name></branch><branch><name>B</name></branch></test-tree>' | ContentType.XML
+ scenario | data | contentType
+ 'JSON data' | '{"branch": [{"name": "A"}, {"name": "B"}]}' | ContentType.JSON
+ 'XML data' | '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>A</name></branch><branch><name>B</name></branch></test-tree>' | ContentType.XML
+
}
- def 'Saving empty list element data fragment for JSON/XML data.'() {
+ def 'Saving empty list element data fragment for #scenario.'() {
given: 'schema set for given anchor and dataspace references test-tree model'
setupSchemaSetMocks('test-tree.yang')
when: 'save data method is invoked with an empty list'
@@ -200,9 +204,9 @@ class CpsDataServiceImplSpec extends Specification {
then: 'invalid data exception is thrown'
thrown(DataValidationException)
where:
- data | contentType
- '{"branch": []}' | ContentType.JSON
- '<test-tree><branch></branch></test-tree>' | ContentType.XML
+ scenario | data | contentType
+ 'JSON data' | '{"branch": []}' | ContentType.JSON
+ 'XML data' | '<test-tree><branch></branch></test-tree>' | ContentType.XML
}
def 'Get all data nodes #scenario.'() {
diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/DataNodeFactorySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/DataNodeFactorySpec.groovy
new file mode 100644
index 0000000000..082fb33a61
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/DataNodeFactorySpec.groovy
@@ -0,0 +1,196 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 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.impl
+
+import ch.qos.logback.classic.Level
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.core.read.ListAppender
+import org.onap.cps.TestUtils
+import org.onap.cps.api.CpsAnchorService
+import org.onap.cps.api.exceptions.DataValidationException
+import org.onap.cps.api.model.Anchor
+import org.onap.cps.utils.ContentType
+import org.onap.cps.utils.YangParser
+import org.onap.cps.utils.YangParserHelper
+import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder
+import org.onap.cps.yang.YangTextSchemaSourceSet
+import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
+import org.slf4j.LoggerFactory
+import org.springframework.context.annotation.AnnotationConfigApplicationContext
+import spock.lang.Specification
+
+class DataNodeFactorySpec extends Specification {
+
+ def mockCpsAnchorService = Mock(CpsAnchorService)
+ def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
+ def mockTimedYangTextSchemaSourceSetBuilder = Mock(TimedYangTextSchemaSourceSetBuilder)
+ def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache, mockTimedYangTextSchemaSourceSetBuilder)
+ def objectUnderTest = new DataNodeFactoryImpl(yangParser)
+
+ def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class)
+ def loggingListAppender
+ def applicationContext = new AnnotationConfigApplicationContext()
+
+ def dataspaceName = 'some-dataspace'
+ def anchorName = 'some-anchor'
+ def schemaSetName = 'some-schema-set'
+ def anchor = Anchor.builder().name(anchorName).dataspaceName(dataspaceName).schemaSetName(schemaSetName).build()
+
+ def setup() {
+ mockCpsAnchorService.getAnchor(dataspaceName, anchorName) >> anchor
+ logger.setLevel(Level.DEBUG)
+ loggingListAppender = new ListAppender()
+ logger.addAppender(loggingListAppender)
+ loggingListAppender.start()
+ applicationContext.refresh()
+ }
+
+ void cleanup() {
+ ((Logger) LoggerFactory.getLogger(DataNodeFactoryImpl.class)).detachAndStopAllAppenders()
+ applicationContext.close()
+ }
+
+ def 'Create data nodes using anchor and map of xpath to #scenario'() {
+ given:'schema set for given anchor and dataspace references test-tree model'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'attempt to create data nodes'
+ def dataNodes = objectUnderTest.createDataNodesWithAnchorAndXpathToNodeData(anchor, xpathToNodeData, contentType)
+ then: 'expected number of data nodes are created'
+ dataNodes.size() == expectedDataNodes
+ and: 'data nodes have expected xpaths'
+ dataNodes.stream().map { it.getXpath() }.toList().containsAll(expectedXpaths)
+ where: 'the following data was used'
+ scenario | xpathToNodeData | contentType || expectedDataNodes | expectedXpaths
+ 'JSON Data' | ['/' : "{'test-tree': {'branch': []}}", '/test-tree' : "{'branch': [{'name':'Name'}]}"] | ContentType.JSON || 2 | ['/test-tree', "/test-tree/branch[@name='Name']"]
+ 'XML Data' | ['/test-tree' : '<branch><name>Name</name></branch>'] | ContentType.XML || 1 | ["/test-tree/branch[@name='Name']"]
+ }
+
+ def 'Create data nodes using anchor, xpath and #scenario string'() {
+ given:'xpath, json string and schema set for given anchor and dataspace references test-tree model'
+ def xpath = '/'
+ def nodeData = TestUtils.getResourceFileContent(data)
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'attempt to create data nodes'
+ def dataNodes = objectUnderTest.createDataNodesWithAnchorXpathAndNodeData(anchor, xpath, nodeData, contentType)
+ then: 'expected number of data nodes are created'
+ dataNodes.size() == 1
+ and: 'data nodes have expected xpaths'
+ dataNodes[0].getXpath() == '/test-tree'
+ where: 'the following data was used'
+ scenario | data | contentType
+ 'JSON' | 'test-tree.json' | ContentType.JSON
+ 'XML' | 'test-tree.xml' | ContentType.XML
+ }
+
+ def 'Building data nodes using anchor, xpath and #scenario'() {
+ given:'xpath, invalid json string and schema set for given anchor and dataspace references test-tree model'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'attempt to create data nodes'
+ objectUnderTest.createDataNodesWithAnchorXpathAndNodeData(anchor, '/test-tree', invalidData, contentType)
+ then: 'expected number of data nodes are created'
+ def exceptionThrown = thrown(DataValidationException)
+ assert exceptionThrown.message.startsWith(expectedMessage)
+ where:
+ scenario | invalidData | contentType || expectedMessage
+ 'no data nodes' | '{}' | ContentType.JSON || 'No Data Nodes'
+ 'invalid json' | '{invalid json' | ContentType.JSON || 'Data Validation Failed'
+ 'invalid xml' | '<invalid xml' | ContentType.XML || 'Data Validation Failed'
+ }
+
+ def 'Create data nodes using anchor, parent node xpath and #scenario string'() {
+ given:'parent node xpath, json string and schema set for given anchor and dataspace references test-tree model'
+ def parentXpath = '/test-tree'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'attempt to create data nodes'
+ def dataNodes = objectUnderTest.createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentXpath, nodeData, contentType)
+ then: 'expected number of data nodes are created'
+ dataNodes.size() == 1
+ and: 'data nodes have expected xpaths'
+ dataNodes[0].getXpath() == "/test-tree/branch[@name='A']"
+ where: 'the following data was used'
+ scenario | nodeData | contentType
+ 'JSON' | '{"branch": [{"name": "A"}]}' | ContentType.JSON
+ 'XML' | '<test-tree xmlns="org:onap:cps:test:test-tree"><branch><name>A</name></branch></test-tree>' | ContentType.XML
+ }
+
+ def 'Create data nodes using anchor, parent node xpath and invalid #scenario string'() {
+ given:'parent node xpath, invalid json string and schema set for given anchor and dataspace references test-tree model'
+ def parentXpath = '/test-tree'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'attempt to create data nodes'
+ objectUnderTest.createDataNodesWithAnchorParentXpathAndNodeData(anchor, parentXpath, invalidData, contentType)
+ then: 'expected number of data nodes are created'
+ def exceptionThrown = thrown(DataValidationException)
+ assert exceptionThrown.message.startsWith(expectedMessage)
+ where:
+ scenario | invalidData | contentType || expectedMessage
+ 'no data nodes' | '{"branch": []}' | ContentType.JSON || 'No Data Nodes'
+ 'invalid json' | '<test-tree><branch></branch></test-tree>' | ContentType.JSON || 'Data Validation Failed'
+ }
+
+ def 'Create data nodes using schema, xpath and #scenario string'() {
+ given:'xpath, json string and schema set for given anchor and dataspace references bookstore model'
+ def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ setupSchemaSetMocksForDelta(yangResourcesNameToContentMap)
+ when: 'attempt to create data nodes'
+ def dataNodes = objectUnderTest.createDataNodesWithYangResourceXpathAndNodeData(yangResourcesNameToContentMap, '/', nodeData, contentType)
+ then: 'expected number of data nodes are created'
+ dataNodes.size() == 1
+ and: 'data nodes have expected xpath'
+ dataNodes[0].getXpath() == '/bookstore'
+ where: 'the following data was used'
+ scenario | nodeData | contentType
+ 'JSON' | '{"bookstore":{"bookstore-name":"Easons"}}' | ContentType.JSON
+ 'XML' | "<bookstore xmlns=\"org:onap:ccsdk:sample\"><bookstore-name>Easons</bookstore-name></bookstore>" | ContentType.XML
+ }
+
+ def 'Create data nodes using schema, xpath and invalid #scenario string'() {
+ given:'xpath, invalid json string and schema set for given anchor and dataspace references bookstore model'
+ def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang')
+ setupSchemaSetMocksForDelta(yangResourcesNameToContentMap)
+ when: 'attempt to create data nodes'
+ objectUnderTest.createDataNodesWithYangResourceXpathAndNodeData(yangResourcesNameToContentMap, '/', invalidData, contentType)
+ then: 'expected number of data nodes are created'
+ def exceptionThrown = thrown(DataValidationException)
+ assert exceptionThrown.message.startsWith(expectedMessage)
+ where:
+ scenario | invalidData | contentType || expectedMessage
+ 'no json nodes' | '{}' | ContentType.JSON || 'No Data Nodes'
+ 'no xml nodes' | '"<bookstore xmlns=\"org:onap:ccsdk:sample\"/>' | ContentType.XML || 'Data Validation Failed'
+ 'invalid json' | '{invalid' | ContentType.JSON || 'Data Validation Failed'
+ 'invalid xml' | '<invalid' | ContentType.XML || 'Data Validation Failed'
+ }
+
+ def setupSchemaSetMocks(String... yangResources) {
+ def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
+ mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
+ def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(yangResources)
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
+ mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
+ }
+
+ def setupSchemaSetMocksForDelta(Map<String, String> yangResourcesNameToContentMap) {
+ def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
+ mockTimedYangTextSchemaSourceSetBuilder.getYangTextSchemaSourceSet(yangResourcesNameToContentMap) >> mockYangTextSchemaSourceSet
+ mockYangTextSchemaSourceSetCache.get(_, _) >> mockYangTextSchemaSourceSet
+ def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap).getSchemaContext()
+ mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
+ }
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/E2ENetworkSliceSpec.groovy
index db5b4f104e..f91570136c 100755
--- a/cps-service/src/test/groovy/org/onap/cps/impl/E2ENetworkSliceSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/E2ENetworkSliceSpec.groovy
@@ -3,7 +3,7 @@
* Copyright (C) 2021-2025 Nordix Foundation.
* Modifications Copyright (C) 2021-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2025 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,7 +57,8 @@ class E2ENetworkSliceSpec extends Specification {
mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService)
- def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator,
+ def dataNodeFactory = new DataNodeFactoryImpl(yangParser)
+ def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockDataUpdateEventsService, mockCpsAnchorService, dataNodeFactory, mockCpsValidator,
yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)
def dataspaceName = 'someDataspace'
def anchorName = 'someAnchor'